Adding hal_inline dialect and runtime module.
This lowers from the stream dialect into a much reduced form of the HAL
dialect that uses a compatible type system with the HAL dialect but a
restricted synchronous/local execution model. Executables translated to
`vmvx-inline` are inlined directly into the host module and the only
thing remaining is `!hal.buffer`/`!hal.buffer_view` management for
ABI compatibility with the full HAL dialect.

The tradeoff here with the full HAL dialect is that this only runs
in-process and synchronously on the VM (bytecode or emitc) and is not
relevant to CUDA/multithreaded CPU/etc. For a single-core embedded
device with VMVX kernels, though, it should be more than enough to run
all models.
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/Passes.cpp b/compiler/src/iree/compiler/Codegen/LLVMCPU/Passes.cpp
index d06a3b5..4230e75 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/Passes.cpp
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/Passes.cpp
@@ -362,6 +362,10 @@
   OpPassManager &nestedModulePM = passManager.nest<ModuleOp>();
   addBufferizePasses(nestedModulePM);
 
+  // Cleanup the IR that may now have unused loops.
+  nestedModulePM.addNestedPass<func::FuncOp>(
+      createRemoveSingleIterationLoopPass());
+
   // Convert buffer-level microkernels.
   if (clEnableMicrokernels) {
     nestedModulePM.addNestedPass<func::FuncOp>(
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/BUILD
new file mode 100644
index 0000000..cadf491
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/BUILD
@@ -0,0 +1,11 @@
+# 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
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/CMakeLists.txt
new file mode 100644
index 0000000..00e2756
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/CMakeLists.txt
@@ -0,0 +1,13 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/BUILD                         #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/BUILD
new file mode 100644
index 0000000..63eef08
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/BUILD
@@ -0,0 +1,22 @@
+# 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
+
+load("//build_tools/embed_data:build_defs.bzl", "c_embed_data")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+c_embed_data(
+    name = "hal_inline_imports",
+    srcs = ["hal_inline.imports.mlir"],
+    c_file_output = "hal_inline.imports.c",
+    flatten = True,
+    h_file_output = "hal_inline.imports.h",
+    identifier = "iree_hal_inline_imports",
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/CMakeLists.txt
new file mode 100644
index 0000000..adaedc2
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/CMakeLists.txt
@@ -0,0 +1,28 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/BUILD                  #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_c_embed_data(
+  NAME
+    hal_inline_imports
+  SRCS
+    "hal_inline.imports.mlir"
+  C_FILE_OUTPUT
+    "hal_inline.imports.c"
+  H_FILE_OUTPUT
+    "hal_inline.imports.h"
+  IDENTIFIER
+    "iree_hal_inline_imports"
+  FLATTEN
+  PUBLIC
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/BUILD
new file mode 100644
index 0000000..cadf491
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/BUILD
@@ -0,0 +1,11 @@
+# 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
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/CMakeLists.txt
new file mode 100644
index 0000000..9f68086
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/CMakeLists.txt
@@ -0,0 +1,13 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/BUILD       #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/BUILD
new file mode 100644
index 0000000..9cfed7d
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/BUILD
@@ -0,0 +1,35 @@
+# 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
+
+load("//build_tools/bazel:build_defs.oss.bzl", "iree_compiler_cc_library")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_compiler_cc_library(
+    name = "HALInlineToVM",
+    srcs = [
+        "ConvertHALInlineToVM.cpp",
+    ],
+    hdrs = [
+        "ConvertHALInlineToVM.h",
+    ],
+    deps = [
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR",
+        "//compiler/src/iree/compiler/Dialect/Util/IR",
+        "//compiler/src/iree/compiler/Dialect/VM/Conversion",
+        "//compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM",
+        "//compiler/src/iree/compiler/Dialect/VM/IR",
+        "@llvm-project//mlir:ArithmeticDialect",
+        "@llvm-project//mlir:FuncDialect",
+        "@llvm-project//mlir:IR",
+        "@llvm-project//mlir:Pass",
+        "@llvm-project//mlir:Transforms",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/CMakeLists.txt
new file mode 100644
index 0000000..681218c
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/CMakeLists.txt
@@ -0,0 +1,34 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/BUILD#
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_cc_library(
+  NAME
+    HALInlineToVM
+  HDRS
+    "ConvertHALInlineToVM.h"
+  SRCS
+    "ConvertHALInlineToVM.cpp"
+  DEPS
+    MLIRArithmeticDialect
+    MLIRFuncDialect
+    MLIRIR
+    MLIRPass
+    MLIRTransforms
+    iree::compiler::Dialect::Modules::HAL::Inline::IR
+    iree::compiler::Dialect::Util::IR
+    iree::compiler::Dialect::VM::Conversion
+    iree::compiler::Dialect::VM::Conversion::StandardToVM
+    iree::compiler::Dialect::VM::IR
+  PUBLIC
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/ConvertHALInlineToVM.cpp b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/ConvertHALInlineToVM.cpp
new file mode 100644
index 0000000..095398e
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/ConvertHALInlineToVM.cpp
@@ -0,0 +1,71 @@
+// 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/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/ConvertHALInlineToVM.h"
+
+#include "iree/compiler/Dialect/Util/IR/UtilTypes.h"
+#include "iree/compiler/Dialect/VM/Conversion/ConversionTarget.h"
+#include "iree/compiler/Dialect/VM/Conversion/ImportUtils.h"
+#include "iree/compiler/Dialect/VM/Conversion/TypeConverter.h"
+#include "iree/compiler/Dialect/VM/IR/VMOps.h"
+#include "mlir/IR/Attributes.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Matchers.h"
+#include "mlir/IR/SymbolTable.h"
+#include "mlir/Transforms/DialectConversion.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+void populateHALInlineToVMPatterns(MLIRContext *context,
+                                   ConversionTarget &conversionTarget,
+                                   TypeConverter &typeConverter,
+                                   SymbolTable &importSymbols,
+                                   RewritePatternSet &patterns) {
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferAllocateOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer.allocate");
+  patterns.insert<
+      VMImportOpConversion<IREE::HAL::Inline::BufferAllocateInitializedOp>>(
+      context, importSymbols, typeConverter,
+      "hal_inline.buffer.allocate.initialized");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferWrapOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer.wrap");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferSubspanOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer.subspan");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferLengthOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer.length");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferStorageOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer.storage");
+
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferViewCreateOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer_view.create");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferViewAssertOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer_view.assert");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferViewBufferOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer_view.buffer");
+  patterns
+      .insert<VMImportOpConversion<IREE::HAL::Inline::BufferViewElementTypeOp>>(
+          context, importSymbols, typeConverter,
+          "hal_inline.buffer_view.element_type");
+  patterns.insert<
+      VMImportOpConversion<IREE::HAL::Inline::BufferViewEncodingTypeOp>>(
+      context, importSymbols, typeConverter,
+      "hal_inline.buffer_view.encoding_type");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferViewRankOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer_view.rank");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferViewDimOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer_view.dim");
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::BufferViewTraceOp>>(
+      context, importSymbols, typeConverter, "hal_inline.buffer_view.trace");
+
+  patterns.insert<VMImportOpConversion<IREE::HAL::Inline::DeviceQueryOp>>(
+      context, importSymbols, typeConverter, "hal_inline.device.query.i64");
+}
+
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/ConvertHALInlineToVM.h b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/ConvertHALInlineToVM.h
new file mode 100644
index 0000000..5c155c4
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/ConvertHALInlineToVM.h
@@ -0,0 +1,27 @@
+// 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_DIALECT_MODULES_HAL_INLINE_CONVERSION_HALINLINE_CONVERTHALINLINETOVM_H_
+#define IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_CONVERSION_HALINLINE_CONVERTHALINLINETOVM_H_
+
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Transforms/DialectConversion.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+// Populates conversion patterns from the hal_inline dialect to the VM dialect.
+void populateHALInlineToVMPatterns(MLIRContext *context,
+                                   ConversionTarget &conversionTarget,
+                                   TypeConverter &typeConverter,
+                                   SymbolTable &importSymbols,
+                                   RewritePatternSet &patterns);
+
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_CONVERSION_HALINLINE_CONVERTHALINLINETOVM_H_
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/test/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/test/BUILD
new file mode 100644
index 0000000..b027b7c
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/test/BUILD
@@ -0,0 +1,28 @@
+# 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
+
+load("//build_tools/bazel:iree_lit_test.bzl", "iree_lit_test_suite")
+load("//build_tools/bazel:enforce_glob.bzl", "enforce_glob")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_lit_test_suite(
+    name = "lit",
+    srcs = enforce_glob(
+        [
+        ],
+        include = ["*.mlir"],
+    ),
+    cfg = "//compiler:lit.cfg.py",
+    tools = [
+        "//tools:iree-opt",
+        "@llvm-project//llvm:FileCheck",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/test/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/test/CMakeLists.txt
new file mode 100644
index 0000000..53c3829
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/test/CMakeLists.txt
@@ -0,0 +1,21 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/test/BUILD#
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_lit_test_suite(
+  NAME
+    lit
+  TOOLS
+    FileCheck
+    iree-opt
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/BUILD
new file mode 100644
index 0000000..6791d99
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/BUILD
@@ -0,0 +1,39 @@
+# 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
+
+load("//build_tools/bazel:build_defs.oss.bzl", "iree_compiler_cc_library")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_compiler_cc_library(
+    name = "HALToHALInline",
+    srcs = [
+        "ConvertHALToHALInline.cpp",
+    ],
+    hdrs = [
+        "ConvertHALToHALInline.h",
+    ],
+    deps = [
+        "//compiler/src/iree/compiler/Dialect/HAL/Conversion",
+        "//compiler/src/iree/compiler/Dialect/HAL/IR",
+        "//compiler/src/iree/compiler/Dialect/HAL/IR:HALDialect",
+        "//compiler/src/iree/compiler/Dialect/HAL/Target",
+        "//compiler/src/iree/compiler/Dialect/HAL/Utils",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR:HALInlineDialect",
+        "//compiler/src/iree/compiler/Dialect/Util/IR",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//mlir:ArithmeticDialect",
+        "@llvm-project//mlir:FuncDialect",
+        "@llvm-project//mlir:IR",
+        "@llvm-project//mlir:Pass",
+        "@llvm-project//mlir:Transforms",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/CMakeLists.txt
new file mode 100644
index 0000000..f8d8546
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/CMakeLists.txt
@@ -0,0 +1,38 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/BUILD#
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_cc_library(
+  NAME
+    HALToHALInline
+  HDRS
+    "ConvertHALToHALInline.h"
+  SRCS
+    "ConvertHALToHALInline.cpp"
+  DEPS
+    LLVMSupport
+    MLIRArithmeticDialect
+    MLIRFuncDialect
+    MLIRIR
+    MLIRPass
+    MLIRTransforms
+    iree::compiler::Dialect::HAL::Conversion
+    iree::compiler::Dialect::HAL::IR
+    iree::compiler::Dialect::HAL::IR::HALDialect
+    iree::compiler::Dialect::HAL::Target
+    iree::compiler::Dialect::HAL::Utils
+    iree::compiler::Dialect::Modules::HAL::Inline::IR
+    iree::compiler::Dialect::Modules::HAL::Inline::IR::HALInlineDialect
+    iree::compiler::Dialect::Util::IR
+  PUBLIC
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/ConvertHALToHALInline.cpp b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/ConvertHALToHALInline.cpp
new file mode 100644
index 0000000..4c53ae9
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/ConvertHALToHALInline.cpp
@@ -0,0 +1,227 @@
+// 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/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/ConvertHALToHALInline.h"
+
+#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/Modules/HAL/Inline/IR/HALInlineDialect.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h"
+#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
+#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Transforms/DialectConversion.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+namespace {
+
+struct BufferSubspanOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferSubspanOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferSubspanOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto bufferType = getTypeConverter()->convertType(op.getResult().getType());
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferSubspanOp>(
+        op, bufferType, adaptor.getSourceBuffer(), adaptor.getSourceOffset(),
+        adaptor.getLength());
+    return success();
+  }
+};
+
+struct BufferLengthOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferLengthOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferLengthOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto sizeType = getTypeConverter()->convertType(op.getResult().getType());
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferLengthOp>(
+        op, sizeType, adaptor.getBuffer());
+    return success();
+  }
+};
+
+struct BufferLoadOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferLoadOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferLoadOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    Value storageBuffer =
+        rewriter.createOrFold<IREE::HAL::Inline::BufferStorageOp>(
+            op.getLoc(), adaptor.getSourceBuffer());
+    Value storageSize = rewriter.create<IREE::HAL::Inline::BufferLengthOp>(
+        op.getLoc(), adaptor.getSourceBuffer());
+    auto loadType = getTypeConverter()->convertType(op.getResult().getType());
+    rewriter.replaceOpWithNewOp<IREE::Util::BufferLoadOp>(
+        op, loadType, storageBuffer, storageSize, adaptor.getSourceOffset());
+    return success();
+  }
+};
+
+struct BufferStoreOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferStoreOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferStoreOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    Value storageBuffer =
+        rewriter.createOrFold<IREE::HAL::Inline::BufferStorageOp>(
+            op.getLoc(), adaptor.getTargetBuffer());
+    Value storageSize = rewriter.create<IREE::HAL::Inline::BufferLengthOp>(
+        op.getLoc(), adaptor.getTargetBuffer());
+    rewriter.replaceOpWithNewOp<IREE::Util::BufferStoreOp>(
+        op, adaptor.getValue(), storageBuffer, storageSize,
+        adaptor.getTargetOffset());
+    return success();
+  }
+};
+
+struct BufferViewCreateOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferViewCreateOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferViewCreateOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewCreateOp>(
+        op, adaptor.getBuffer(), adaptor.getElementType(),
+        adaptor.getEncodingType(), adaptor.getShape());
+    return success();
+  }
+};
+
+struct BufferViewBufferOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferViewBufferOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferViewBufferOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewBufferOp>(
+        op, rewriter.getType<IREE::HAL::BufferType>(), adaptor.getBufferView());
+    return success();
+  }
+};
+
+struct BufferViewAssertOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferViewAssertOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferViewAssertOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewAssertOp>(
+        op, adaptor.getBufferView(), adaptor.getMessage(),
+        adaptor.getElementType(), adaptor.getEncodingType(),
+        adaptor.getShape());
+    return success();
+  }
+};
+
+struct BufferViewElementTypeOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferViewElementTypeOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferViewElementTypeOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewElementTypeOp>(
+        op, op.getResult().getType(), adaptor.getBufferView());
+    return success();
+  }
+};
+
+struct BufferViewEncodingTypeOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferViewEncodingTypeOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferViewEncodingTypeOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewEncodingTypeOp>(
+        op, op.getResult().getType(), adaptor.getBufferView());
+    return success();
+  }
+};
+
+struct BufferViewRankOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferViewRankOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferViewRankOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewRankOp>(
+        op, op.getResult().getType(), adaptor.getBufferView());
+    return success();
+  }
+};
+
+struct BufferViewDimOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferViewDimOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferViewDimOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewDimOp>(
+        op, op.getResult().getType(), adaptor.getBufferView(),
+        adaptor.getIndexAttr());
+    return success();
+  }
+};
+
+struct BufferViewTraceOpPattern
+    : public OpConversionPattern<IREE::HAL::BufferViewTraceOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::HAL::BufferViewTraceOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewTraceOp>(
+        op, adaptor.getKeyAttr(), adaptor.getOperands());
+    return success();
+  }
+};
+
+}  // namespace
+
+void populateHALToHALInlinePatterns(MLIRContext *context,
+                                    ConversionTarget &conversionTarget,
+                                    TypeConverter &typeConverter,
+                                    RewritePatternSet &patterns) {
+  typeConverter.addConversion([](IREE::HAL::BufferType type) { return type; });
+  typeConverter.addConversion(
+      [](IREE::HAL::BufferViewType type) { return type; });
+
+  typeConverter.addTargetMaterialization(
+      [](OpBuilder &builder, IREE::Util::BufferType type, ValueRange inputs,
+         Location loc) -> Value {
+        assert(inputs.size() == 1);
+        if (inputs[0].getType().isa<IREE::HAL::BufferType>()) {
+          return builder.createOrFold<IREE::HAL::Inline::BufferStorageOp>(
+              loc, inputs[0]);
+        } else {
+          emitError(loc) << "unsupported HAL inline target materialization: "
+                         << inputs[0].getType();
+          return nullptr;
+        }
+      });
+
+  patterns.insert<BufferSubspanOpPattern>(typeConverter, context);
+  patterns.insert<BufferLengthOpPattern>(typeConverter, context);
+  patterns.insert<BufferLoadOpPattern>(typeConverter, context);
+  patterns.insert<BufferStoreOpPattern>(typeConverter, context);
+
+  patterns.insert<BufferViewCreateOpPattern>(typeConverter, context);
+  patterns.insert<BufferViewAssertOpPattern>(typeConverter, context);
+  patterns.insert<BufferViewBufferOpPattern>(typeConverter, context);
+  patterns.insert<BufferViewElementTypeOpPattern>(typeConverter, context);
+  patterns.insert<BufferViewEncodingTypeOpPattern>(typeConverter, context);
+  patterns.insert<BufferViewRankOpPattern>(typeConverter, context);
+  patterns.insert<BufferViewDimOpPattern>(typeConverter, context);
+  patterns.insert<BufferViewTraceOpPattern>(typeConverter, context);
+}
+
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/ConvertHALToHALInline.h b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/ConvertHALToHALInline.h
new file mode 100644
index 0000000..19c402f
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/ConvertHALToHALInline.h
@@ -0,0 +1,25 @@
+// 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_DIALECT_MODULES_HAL_INLINE_CONVERSION_HALTOHALINLINE_CONVERTHALTOHALINLINE_H_
+#define IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_CONVERSION_HALTOHALINLINE_CONVERTHALTOHALINLINE_H_
+
+#include "mlir/Pass/Pass.h"
+#include "mlir/Transforms/DialectConversion.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+// Populates conversion patterns for full HAL -> inline HAL.
+void populateHALToHALInlinePatterns(MLIRContext *context,
+                                    ConversionTarget &conversionTarget,
+                                    TypeConverter &typeConverter,
+                                    RewritePatternSet &patterns);
+
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_CONVERSION_HALTOHALINLINE_CONVERTHALTOHALINLINE_H_
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/BUILD
new file mode 100644
index 0000000..c3b3094
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/BUILD
@@ -0,0 +1,29 @@
+# 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
+
+load("//build_tools/bazel:iree_lit_test.bzl", "iree_lit_test_suite")
+load("//build_tools/bazel:enforce_glob.bzl", "enforce_glob")
+
+package(
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_lit_test_suite(
+    name = "lit",
+    srcs = enforce_glob(
+        [
+            "buffer_ops.mlir",
+            "buffer_view_ops.mlir",
+        ],
+        include = ["*.mlir"],
+    ),
+    cfg = "//compiler:lit.cfg.py",
+    tools = [
+        "//tools:iree-opt",
+        "@llvm-project//llvm:FileCheck",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/CMakeLists.txt
new file mode 100644
index 0000000..fa35f20
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/CMakeLists.txt
@@ -0,0 +1,24 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/BUILD#
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_lit_test_suite(
+  NAME
+    lit
+  SRCS
+    "buffer_ops.mlir"
+    "buffer_view_ops.mlir"
+  TOOLS
+    FileCheck
+    iree-opt
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/buffer_ops.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/buffer_ops.mlir
new file mode 100644
index 0000000..b6ce93a
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/buffer_ops.mlir
@@ -0,0 +1,53 @@
+// RUN: iree-opt --split-input-file --iree-hal-inline-conversion %s | FileCheck %s
+
+// CHECK-LABEL: @buffer_subspan
+// CHECK-SAME: (%[[BUFFER:.+]]: !hal.buffer)
+func.func @buffer_subspan(%buffer: !hal.buffer) -> !hal.buffer {
+  // CHECK-DAG: %[[OFFSET:.+]] = arith.constant 100
+  %offset = arith.constant 100 : index
+  // CHECK-DAG: %[[LENGTH:.+]] = arith.constant 200
+  %length = arith.constant 200 : index
+  // CHECK: %[[SUBSPAN:.+]] = hal_inline.buffer.subspan<%[[BUFFER]] : !hal.buffer>[%[[OFFSET]], %[[LENGTH]]] : !hal.buffer
+  %subspan = hal.buffer.subspan<%buffer : !hal.buffer>[%offset, %length] : !hal.buffer
+  // CHECK: return %[[SUBSPAN]]
+  return %subspan : !hal.buffer
+}
+
+// -----
+
+// CHECK-LABEL: @buffer_length
+// CHECK-SAME: (%[[BUFFER:.+]]: !hal.buffer)
+func.func @buffer_length(%buffer: !hal.buffer) -> index {
+  // CHECK: hal_inline.buffer.length<%[[BUFFER]] : !hal.buffer> : index
+  %length = hal.buffer.length<%buffer : !hal.buffer> : index
+  return %length : index
+}
+
+// -----
+
+// CHECK-LABEL: @buffer_load
+// CHECK-SAME: (%[[BUFFER:.+]]: !hal.buffer)
+func.func @buffer_load(%buffer: !hal.buffer) -> i32 {
+  // CHECK-DAG: %[[REL_OFFSET:.+]] = arith.constant 100
+  %rel_offset = arith.constant 100 : index
+  // CHECK-DAG: %[[STORAGE:.+]] = hal_inline.buffer.storage<%[[BUFFER:.+]] : !hal.buffer> : !util.buffer
+  // CHECK-DAG: %[[LENGTH:.+]] = hal_inline.buffer.length<%[[BUFFER]] : !hal.buffer> : index
+  // CHECK: %[[VALUE:.+]] = util.buffer.load %[[STORAGE]][%[[REL_OFFSET]]] : !util.buffer{%[[LENGTH]]} -> i32
+  %value = hal.buffer.load<%buffer : !hal.buffer>[%rel_offset] : i32
+  // CHECK-NEXT: return %[[VALUE]]
+  return %value : i32
+}
+
+// -----
+
+// CHECK-LABEL: @buffer_store
+// CHECK-SAME: (%[[BUFFER:.+]]: !hal.buffer, %[[VALUE:.+]]: i32)
+func.func @buffer_store(%buffer: !hal.buffer, %value: i32) {
+  // CHECK-DAG: %[[REL_OFFSET:.+]] = arith.constant 100
+  %rel_offset = arith.constant 100 : index
+  // CHECK-DAG: %[[STORAGE:.+]] = hal_inline.buffer.storage<%[[BUFFER:.+]] : !hal.buffer> : !util.buffer
+  // CHECK-DAG: %[[LENGTH:.+]] = hal_inline.buffer.length<%[[BUFFER]] : !hal.buffer> : index
+  // CHECK: util.buffer.store %[[VALUE]], %[[STORAGE]][%[[REL_OFFSET]]] : i32 -> !util.buffer{%[[LENGTH]]}
+  hal.buffer.store<%buffer : !hal.buffer>[%rel_offset] value(%value : i32)
+  return
+}
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/buffer_view_ops.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/buffer_view_ops.mlir
new file mode 100644
index 0000000..c65b1c0
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/test/buffer_view_ops.mlir
@@ -0,0 +1,37 @@
+// RUN: iree-opt --split-input-file --iree-hal-inline-conversion %s | FileCheck %s
+
+// CHECK-LABEL: @buffer_view_create
+func.func @buffer_view_create(%arg0: !hal.buffer, %arg1: index, %arg2: index) -> !hal.buffer_view {
+  %c1 = arith.constant 1 : i32
+  %c32 = arith.constant 32 : i32
+  // CHECK: %view = hal_inline.buffer_view.create
+  // CHECK-SAME: buffer(%arg0 : !hal.buffer)
+  // CHECK-SAME: shape([%arg1, %arg2])
+  // CHECK-SAME: type(%c32_i32)
+  // CHECK-SAME: encoding(%c1_i32) : !hal.buffer_view
+  %view = hal.buffer_view.create buffer(%arg0 : !hal.buffer)
+                                 shape([%arg1, %arg2])
+                                 type(%c32)
+                                 encoding(%c1) : !hal.buffer_view
+  return %view : !hal.buffer_view
+}
+
+// -----
+
+// CHECK-LABEL: @buffer_view_buffer
+func.func @buffer_view_buffer(%arg0: !hal.buffer_view) -> !hal.buffer {
+  // CHECK: %buffer = hal_inline.buffer_view.buffer<%arg0 : !hal.buffer_view> : !hal.buffer
+  %buffer = hal.buffer_view.buffer<%arg0 : !hal.buffer_view> : !hal.buffer
+  return %buffer : !hal.buffer
+}
+
+// -----
+
+// CHECK-LABEL: @buffer_view_shape_queries
+func.func @buffer_view_shape_queries(%arg0: !hal.buffer_view) -> (index, index) {
+  // CHECK: %{{.+}} = hal_inline.buffer_view.rank<%arg0 : !hal.buffer_view> : index
+  %0 = hal.buffer_view.rank<%arg0 : !hal.buffer_view> : index
+  // CHECK: %{{.+}} = hal_inline.buffer_view.dim<%arg0 : !hal.buffer_view>[0] : index
+  %1 = hal.buffer_view.dim<%arg0 : !hal.buffer_view>[0] : index
+  return %0, %1 : index, index
+}
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/BUILD
new file mode 100644
index 0000000..80498a2
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/BUILD
@@ -0,0 +1,40 @@
+# 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
+
+load("//build_tools/bazel:build_defs.oss.bzl", "iree_compiler_cc_library")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_compiler_cc_library(
+    name = "StreamToHALInline",
+    srcs = [
+        "ConvertStreamToHALInline.cpp",
+    ],
+    hdrs = [
+        "ConvertStreamToHALInline.h",
+    ],
+    deps = [
+        "//compiler/src/iree/compiler/Dialect/HAL/Conversion",
+        "//compiler/src/iree/compiler/Dialect/HAL/IR",
+        "//compiler/src/iree/compiler/Dialect/HAL/IR:HALDialect",
+        "//compiler/src/iree/compiler/Dialect/HAL/Target",
+        "//compiler/src/iree/compiler/Dialect/HAL/Utils",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR:HALInlineDialect",
+        "//compiler/src/iree/compiler/Dialect/Stream/IR",
+        "//compiler/src/iree/compiler/Dialect/Util/IR",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//mlir:ArithmeticDialect",
+        "@llvm-project//mlir:FuncDialect",
+        "@llvm-project//mlir:IR",
+        "@llvm-project//mlir:Pass",
+        "@llvm-project//mlir:Transforms",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/CMakeLists.txt
new file mode 100644
index 0000000..b688989
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/CMakeLists.txt
@@ -0,0 +1,39 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/BUILD#
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_cc_library(
+  NAME
+    StreamToHALInline
+  HDRS
+    "ConvertStreamToHALInline.h"
+  SRCS
+    "ConvertStreamToHALInline.cpp"
+  DEPS
+    LLVMSupport
+    MLIRArithmeticDialect
+    MLIRFuncDialect
+    MLIRIR
+    MLIRPass
+    MLIRTransforms
+    iree::compiler::Dialect::HAL::Conversion
+    iree::compiler::Dialect::HAL::IR
+    iree::compiler::Dialect::HAL::IR::HALDialect
+    iree::compiler::Dialect::HAL::Target
+    iree::compiler::Dialect::HAL::Utils
+    iree::compiler::Dialect::Modules::HAL::Inline::IR
+    iree::compiler::Dialect::Modules::HAL::Inline::IR::HALInlineDialect
+    iree::compiler::Dialect::Stream::IR
+    iree::compiler::Dialect::Util::IR
+  PUBLIC
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/ConvertStreamToHALInline.cpp b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/ConvertStreamToHALInline.cpp
new file mode 100644
index 0000000..a073c7d
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/ConvertStreamToHALInline.cpp
@@ -0,0 +1,628 @@
+// 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/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/ConvertStreamToHALInline.h"
+
+#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
+#include "iree/compiler/Dialect/HAL/IR/HALTypes.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h"
+#include "iree/compiler/Dialect/Stream/IR/StreamDialect.h"
+#include "iree/compiler/Dialect/Stream/IR/StreamOps.h"
+#include "iree/compiler/Dialect/Stream/IR/StreamTypes.h"
+#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
+#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Transforms/DialectConversion.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+namespace {
+
+static Value getResourceSize(Location loc, Value resource, OpBuilder &builder) {
+  if (resource.getType().isa<IREE::HAL::BufferType>()) {
+    return builder.createOrFold<IREE::HAL::Inline::BufferLengthOp>(
+        loc, builder.getIndexType(), resource);
+  }
+  return builder.createOrFold<IREE::Util::BufferSizeOp>(
+      loc, builder.getIndexType(), resource);
+}
+
+struct Storage {
+  // Underlying storage buffer.
+  Value buffer;
+  // Total size of the storage buffer in bytes.
+  Value bufferSize;
+};
+
+static Storage getResourceStorage(Location loc, Value resource,
+                                  Value resourceSize, OpBuilder &builder) {
+  if (resource.getType().isa<IREE::HAL::BufferType>()) {
+    // Get the storage of the buffer; the returned buffer is already a subspan.
+    auto storageBuffer =
+        builder.createOrFold<IREE::HAL::Inline::BufferStorageOp>(loc, resource);
+    auto storageSize = getResourceSize(loc, resource, builder);
+    return {
+        storageBuffer,
+        storageSize,
+    };
+  }
+  return {
+      resource,
+      resourceSize,
+  };
+}
+
+struct ResourceAllocOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceAllocOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceAllocOp allocOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto deviceBufferType = rewriter.getType<IREE::HAL::BufferType>();
+    auto hostBufferType = rewriter.getType<IREE::Util::BufferType>();
+
+    // For now we don't have this information and assume something conservative.
+    Value minAlignment =
+        rewriter.create<arith::ConstantIndexOp>(allocOp.getLoc(), 64);
+
+    SmallVector<Value> results;
+    for (auto [resourceResult, storageSize] :
+         llvm::zip(allocOp.getResults(), allocOp.getStorageSizes())) {
+      auto allocateOp = rewriter.create<IREE::HAL::Inline::BufferAllocateOp>(
+          allocOp.getLoc(), deviceBufferType, hostBufferType, minAlignment,
+          storageSize);
+      results.push_back(allocateOp.getResult());
+    }
+
+    rewriter.replaceOp(allocOp, results);
+    return success();
+  }
+};
+
+struct ResourceAllocaOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceAllocaOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceAllocaOp allocaOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto deviceBufferType = rewriter.getType<IREE::HAL::BufferType>();
+    auto hostBufferType = rewriter.getType<IREE::Util::BufferType>();
+
+    // For now we don't have this information and assume something conservative.
+    Value minAlignment =
+        rewriter.create<arith::ConstantIndexOp>(allocaOp.getLoc(), 64);
+    auto allocateOp = rewriter.create<IREE::HAL::Inline::BufferAllocateOp>(
+        allocaOp.getLoc(), deviceBufferType, hostBufferType, minAlignment,
+        adaptor.getStorageSize());
+
+    auto resolvedTimepoint =
+        rewriter.create<arith::ConstantIntOp>(allocaOp.getLoc(), 0, 64)
+            .getResult();
+
+    rewriter.replaceOp(allocaOp, {allocateOp.getResult(), resolvedTimepoint});
+    return success();
+  }
+};
+
+struct ResourceDeallocaOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceDeallocaOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceDeallocaOp deallocaOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    // TODO(benvanik): discard op?
+    auto resolvedTimepoint =
+        rewriter.create<arith::ConstantIntOp>(deallocaOp.getLoc(), 0, 64)
+            .getResult();
+    rewriter.replaceOp(deallocaOp, {resolvedTimepoint});
+    return success();
+  }
+};
+
+struct ResourceSizeOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceSizeOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceSizeOp sizeOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOp(sizeOp, getResourceSize(sizeOp.getLoc(),
+                                               adaptor.getOperand(), rewriter));
+    return success();
+  }
+};
+
+// The staging buffer returned from this is always a !util.buffer.
+// We can thus directly pass along the input buffer that's being mapped
+// (after taking a subspan for the defined range).
+struct ResourceMapOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceMapOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceMapOp mapOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::Util::BufferSubspanOp>(
+        mapOp, adaptor.getSource(),
+        getResourceSize(mapOp.getLoc(), adaptor.getSource(), rewriter),
+        adaptor.getSourceOffset(), adaptor.getResultSize());
+    return success();
+  }
+};
+
+// The constant buffer returned from this is always a !util.buffer.
+// We can thus directly pass along the input buffer that's being mapped
+// (after taking a subspan for the defined range).
+struct ResourceTryMapOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceTryMapOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceTryMapOp tryMapOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    Value subspan = rewriter.create<IREE::Util::BufferSubspanOp>(
+        tryMapOp.getLoc(), adaptor.getSource(),
+        getResourceSize(tryMapOp.getLoc(), adaptor.getSource(), rewriter),
+        adaptor.getSourceOffset(), adaptor.getResultSize());
+    Value didMap =
+        rewriter.create<arith::ConstantIntOp>(tryMapOp.getLoc(), 1, 1);
+    rewriter.replaceOp(tryMapOp, {didMap, subspan});
+    return success();
+  }
+};
+
+struct ResourceLoadOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceLoadOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceLoadOp loadOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto loc = loadOp.getLoc();
+    auto storage = getResourceStorage(loc, adaptor.getSource(),
+                                      adaptor.getSourceSize(), rewriter);
+    auto loadType =
+        getTypeConverter()->convertType(loadOp.getResult().getType());
+    rewriter.replaceOpWithNewOp<IREE::Util::BufferLoadOp>(
+        loadOp, loadType, storage.buffer, storage.bufferSize,
+        adaptor.getSourceOffset());
+    return success();
+  }
+};
+
+struct ResourceStoreOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceStoreOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceStoreOp storeOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto loc = storeOp.getLoc();
+    auto storage = getResourceStorage(loc, adaptor.getTarget(),
+                                      adaptor.getTargetSize(), rewriter);
+    rewriter.replaceOpWithNewOp<IREE::Util::BufferStoreOp>(
+        storeOp, adaptor.getValue(), storage.buffer, storage.bufferSize,
+        adaptor.getTargetOffset());
+    return success();
+  }
+};
+
+struct ResourceSubviewOpPattern
+    : public OpConversionPattern<IREE::Stream::ResourceSubviewOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::ResourceSubviewOp subviewOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    if (adaptor.getSource().getType().isa<IREE::HAL::BufferType>()) {
+      auto bufferType = rewriter.getType<IREE::HAL::BufferType>();
+      // NOTE: this aliases! We assume at this point all useful alias analysis
+      // has been performed and it's fine to lose the tie information here.
+      rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferSubspanOp>(
+          subviewOp, bufferType, adaptor.getSource(), adaptor.getSourceOffset(),
+          adaptor.getResultSize());
+    } else {
+      rewriter.replaceOpWithNewOp<IREE::Util::BufferSubspanOp>(
+          subviewOp, adaptor.getSource(), adaptor.getSourceSize(),
+          adaptor.getSourceOffset(), adaptor.getResultSize());
+    }
+    return success();
+  }
+};
+
+struct TensorImportBufferOpPattern
+    : public OpConversionPattern<IREE::Stream::TensorImportOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TensorImportOp importOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    if (!importOp.getSource().getType().isa<IREE::HAL::BufferType>()) {
+      return failure();
+    }
+
+    // Directly use the buffer.
+    auto buffer = adaptor.getSource();
+    rewriter.replaceOp(importOp, buffer);
+    return success();
+  }
+};
+
+struct TensorImportBufferViewOpPattern
+    : public OpConversionPattern<IREE::Stream::TensorImportOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TensorImportOp importOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto sourceType = importOp.getSource().getType();
+    if (!sourceType.isa<IREE::HAL::BufferViewType>() &&
+        !sourceType.isa<TensorType>()) {
+      return failure();
+    }
+
+    auto bufferView = adaptor.getSource();
+    auto bufferType = rewriter.getType<IREE::HAL::BufferType>();
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewBufferOp>(
+        importOp, bufferType, bufferView);
+    return success();
+  }
+};
+
+struct TensorExportBufferOpPattern
+    : public OpConversionPattern<IREE::Stream::TensorExportOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TensorExportOp exportOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    if (!exportOp.getResult().getType().isa<IREE::HAL::BufferType>()) {
+      return failure();
+    }
+    rewriter.replaceOp(exportOp, adaptor.getSource());
+    return success();
+  }
+};
+
+struct TensorExportBufferViewOpPattern
+    : public OpConversionPattern<IREE::Stream::TensorExportOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TensorExportOp exportOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto targetType = exportOp.getResult().getType();
+    if (!targetType.isa<IREE::HAL::BufferViewType>() &&
+        !targetType.isa<TensorType>()) {
+      return failure();
+    }
+
+    auto loc = exportOp.getLoc();
+    auto tensorType = adaptor.getSourceEncoding().cast<RankedTensorType>();
+    auto dynamicDims = adaptor.getSourceEncodingDims();
+
+    // NOTE: we should have verified supported encodings/types at entry into the
+    // HAL pipeline.
+    auto encodingType =
+        IREE::HAL::getEncodingTypeValue(tensorType.getEncoding());
+    assert(encodingType.has_value() && "invalid tensor encoding");
+    auto elementType =
+        IREE::HAL::getElementTypeValue(tensorType.getElementType());
+    assert(elementType.has_value() && "invalid tensor element type");
+
+    // Flatten static + dynamic shape dimensions.
+    SmallVector<Value> dims;
+    unsigned dynamicIdx = 0;
+    for (int64_t idx = 0; idx < tensorType.getRank(); ++idx) {
+      if (tensorType.isDynamicDim(idx)) {
+        dims.push_back(dynamicDims[dynamicIdx++]);
+      } else {
+        dims.push_back(rewriter.create<arith::ConstantIndexOp>(
+            loc, tensorType.getDimSize(idx)));
+      }
+    }
+
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewCreateOp>(
+        exportOp, adaptor.getSource(), elementType.value(),
+        encodingType.value(), dims);
+    return success();
+  }
+};
+
+struct TensorTraceOpPattern
+    : public OpConversionPattern<IREE::Stream::TensorTraceOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TensorTraceOp traceOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<IREE::HAL::Inline::BufferViewTraceOp>(
+        traceOp, traceOp.getKeyAttr(), adaptor.getOperands());
+    return success();
+  }
+};
+
+struct CmdFlushOpPattern
+    : public OpConversionPattern<IREE::Stream::CmdFlushOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdFlushOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.eraseOp(op);
+    return success();
+  }
+};
+
+struct CmdInvalidateOpPattern
+    : public OpConversionPattern<IREE::Stream::CmdInvalidateOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdInvalidateOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.eraseOp(op);
+    return success();
+  }
+};
+
+struct CmdDiscardOpPattern
+    : public OpConversionPattern<IREE::Stream::CmdDiscardOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdDiscardOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.eraseOp(op);
+    return success();
+  }
+};
+
+struct CmdFillOpPattern : public OpConversionPattern<IREE::Stream::CmdFillOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdFillOp fillOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto loc = fillOp.getLoc();
+    auto storage = getResourceStorage(loc, adaptor.getTarget(),
+                                      adaptor.getTargetSize(), rewriter);
+    rewriter.replaceOpWithNewOp<IREE::Util::BufferFillOp>(
+        fillOp, adaptor.getValue(), storage.buffer, storage.bufferSize,
+        adaptor.getTargetOffset(), adaptor.getTargetLength());
+    return success();
+  }
+};
+
+struct CmdCopyOpPattern : public OpConversionPattern<IREE::Stream::CmdCopyOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdCopyOp copyOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto loc = copyOp.getLoc();
+    auto sourceStorage = getResourceStorage(loc, adaptor.getSource(),
+                                            adaptor.getSourceSize(), rewriter);
+    auto targetStorage = getResourceStorage(loc, adaptor.getTarget(),
+                                            adaptor.getTargetSize(), rewriter);
+    rewriter.replaceOpWithNewOp<IREE::Util::BufferCopyOp>(
+        copyOp, sourceStorage.buffer, sourceStorage.bufferSize,
+        adaptor.getSourceOffset(), targetStorage.buffer,
+        targetStorage.bufferSize, adaptor.getTargetOffset(),
+        adaptor.getLength());
+    return success();
+  }
+};
+
+struct CmdDispatchOpPattern
+    : public OpConversionPattern<IREE::Stream::CmdDispatchOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdDispatchOp dispatchOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto loc = dispatchOp.getLoc();
+
+    auto callee = dispatchOp->getAttrOfType<SymbolRefAttr>("hal_inline.target");
+    if (!callee) {
+      return rewriter.notifyMatchFailure(
+          dispatchOp,
+          "missing hal_inline.target annotation from the "
+          "--iree-hal-inline-executables pass");
+    }
+
+    // The InlineExecutables pass has already done the hard work here; we just
+    // need to make a function call to the annotated target function with all
+    // operands/bindings.
+    SmallVector<Value> callArgs;
+    llvm::append_range(callArgs, adaptor.getWorkload());
+    llvm::append_range(callArgs, adaptor.getUniformOperands());
+    SmallVector<Value> bindingBuffers;
+    SmallVector<Value> bindingOffsets;
+    for (auto [resource, resourceSize, resourceOffset] :
+         llvm::zip(adaptor.getResources(), adaptor.getResourceSizes(),
+                   adaptor.getResourceOffsets())) {
+      auto storage = getResourceStorage(loc, resource, resourceSize, rewriter);
+      bindingBuffers.push_back(storage.buffer);
+      bindingOffsets.push_back(resourceOffset);
+    }
+    llvm::append_range(callArgs, bindingBuffers);
+    llvm::append_range(callArgs, bindingOffsets);
+    llvm::append_range(callArgs, adaptor.getResourceLengths());
+    rewriter.replaceOpWithNewOp<func::CallOp>(dispatchOp, callee, TypeRange{},
+                                              callArgs);
+    return success();
+  }
+};
+
+struct CmdExecuteOpPattern
+    : public OpConversionPattern<IREE::Stream::CmdExecuteOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdExecuteOp executeOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    // Inline the serial execution region.
+    rewriter.mergeBlockBefore(&executeOp.getBody().front(), executeOp,
+                              adaptor.getResourceOperands());
+    // Immediately resolve the timepoint.
+    auto resolvedTimepoint =
+        rewriter.create<arith::ConstantIntOp>(executeOp.getLoc(), 0, 64)
+            .getResult();
+    rewriter.replaceOp(executeOp, resolvedTimepoint);
+    return success();
+  }
+};
+
+struct CmdSerialOpPattern
+    : public OpConversionPattern<IREE::Stream::CmdSerialOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdSerialOp serialOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    // Inline the serial execution region.
+    rewriter.mergeBlockBefore(&serialOp.getBody().front(), serialOp);
+    rewriter.eraseOp(serialOp);
+    return success();
+  }
+};
+
+struct CmdConcurrentOpPattern
+    : public OpConversionPattern<IREE::Stream::CmdConcurrentOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::CmdConcurrentOp concurrentOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    // Inline the concurrent execution region.
+    rewriter.mergeBlockBefore(&concurrentOp.getBody().front(), concurrentOp);
+    rewriter.eraseOp(concurrentOp);
+    return success();
+  }
+};
+
+// Annoying we have to have this here, but there's no attribute converter
+// equivalent we have access to so that we could do it in a generic way.
+struct GlobalTimepointConversionPattern
+    : public OpConversionPattern<IREE::Util::GlobalOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Util::GlobalOp op, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    auto initialValue = op.getInitialValue();
+    if (!initialValue.has_value()) return failure();
+    if (!initialValue->isa<IREE::Stream::TimepointAttr>()) return failure();
+    rewriter.updateRootInPlace(
+        op, [&]() { op.setInitialValueAttr(rewriter.getI64IntegerAttr(0)); });
+    return success();
+  }
+};
+
+struct TimepointImmediateOpPattern
+    : public OpConversionPattern<IREE::Stream::TimepointImmediateOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TimepointImmediateOp immediateOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<arith::ConstantIntOp>(immediateOp, 0, 64);
+    return success();
+  }
+};
+
+struct TimepointImportOpPattern
+    : public OpConversionPattern<IREE::Stream::TimepointImportOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TimepointImportOp importOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    return rewriter.notifyMatchFailure(
+        importOp,
+        "timepoints are not supported across the ABI with inline execution");
+  }
+};
+
+struct TimepointExportOpPattern
+    : public OpConversionPattern<IREE::Stream::TimepointExportOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TimepointExportOp exportOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    return rewriter.notifyMatchFailure(
+        exportOp,
+        "timepoints are not supported across the ABI with inline execution");
+  }
+};
+
+struct TimepointJoinOpPattern
+    : public OpConversionPattern<IREE::Stream::TimepointJoinOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TimepointJoinOp joinOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOpWithNewOp<arith::ConstantIntOp>(joinOp, 0, 64);
+    return success();
+  }
+};
+
+struct TimepointAwaitOpPattern
+    : public OpConversionPattern<IREE::Stream::TimepointAwaitOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::TimepointAwaitOp awaitOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.replaceOp(awaitOp, adaptor.getResourceOperands());
+    return success();
+  }
+};
+
+struct ElideYieldOpPattern : public OpConversionPattern<IREE::Stream::YieldOp> {
+  using OpConversionPattern::OpConversionPattern;
+  LogicalResult matchAndRewrite(
+      IREE::Stream::YieldOp yieldOp, OpAdaptor adaptor,
+      ConversionPatternRewriter &rewriter) const override {
+    rewriter.eraseOp(yieldOp);
+    return success();
+  }
+};
+
+}  // namespace
+
+void populateStreamToHALInlinePatterns(MLIRContext *context,
+                                       ConversionTarget &conversionTarget,
+                                       TypeConverter &typeConverter,
+                                       RewritePatternSet &patterns) {
+  typeConverter.addConversion(
+      [=](IREE::Stream::ResourceType type, SmallVectorImpl<Type> &results) {
+        // Resources are just buffers (no shape/encoding/etc).
+        // We use !hal.buffer when going across the external ABI boundary but
+        // otherwise use memrefs.
+        if (type.getLifetime() == IREE::Stream::Lifetime::External) {
+          results.push_back(IREE::HAL::BufferType::get(context));
+        } else {
+          results.push_back(IREE::Util::BufferType::get(context));
+        }
+        return success();
+      });
+
+  typeConverter.addConversion(
+      [=](IREE::Stream::TimepointType type, SmallVectorImpl<Type> &results) {
+        // TODO(benvanik): model timepoints as semaphores.
+        // This may become a !hal.semaphore + index, or some !hal.timepoint that
+        // we then do more analysis on once we know what devices are in use
+        // where.
+        results.push_back(IntegerType::get(context, 64));
+        return success();
+      });
+
+  patterns.insert<ResourceAllocOpPattern, ResourceAllocaOpPattern,
+                  ResourceDeallocaOpPattern, ResourceSizeOpPattern,
+                  ResourceMapOpPattern, ResourceTryMapOpPattern,
+                  ResourceLoadOpPattern, ResourceStoreOpPattern,
+                  ResourceSubviewOpPattern>(typeConverter, context);
+
+  patterns.insert<TensorImportBufferOpPattern, TensorImportBufferViewOpPattern,
+                  TensorExportBufferOpPattern, TensorExportBufferViewOpPattern,
+                  TensorTraceOpPattern>(typeConverter, context);
+
+  patterns
+      .insert<CmdFlushOpPattern, CmdInvalidateOpPattern, CmdDiscardOpPattern,
+              CmdFillOpPattern, CmdCopyOpPattern, CmdDispatchOpPattern,
+              CmdExecuteOpPattern, CmdSerialOpPattern, CmdConcurrentOpPattern>(
+          typeConverter, context);
+
+  patterns.insert<GlobalTimepointConversionPattern>(typeConverter, context);
+  patterns.insert<TimepointImmediateOpPattern, TimepointImportOpPattern,
+                  TimepointExportOpPattern, TimepointJoinOpPattern,
+                  TimepointAwaitOpPattern>(typeConverter, context);
+
+  patterns.insert<ElideYieldOpPattern>(typeConverter, context);
+}
+
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/ConvertStreamToHALInline.h b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/ConvertStreamToHALInline.h
new file mode 100644
index 0000000..5a6e529
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/ConvertStreamToHALInline.h
@@ -0,0 +1,25 @@
+// 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_DIALECT_MODULES_HAL_INLINE_CONVERSION_STREAMTOHALINLINE_CONVERTSTREAMTOHALINLINE_H_
+#define IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_CONVERSION_STREAMTOHALINLINE_CONVERTSTREAMTOHALINLINE_H_
+
+#include "mlir/Pass/Pass.h"
+#include "mlir/Transforms/DialectConversion.h"
+
+namespace mlir {
+namespace iree_compiler {
+
+// Populates conversion patterns for stream->HAL (inline).
+void populateStreamToHALInlinePatterns(MLIRContext *context,
+                                       ConversionTarget &conversionTarget,
+                                       TypeConverter &typeConverter,
+                                       RewritePatternSet &patterns);
+
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_CONVERSION_STREAMTOHALINLINE_CONVERTSTREAMTOHALINLINE_H_
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/BUILD
new file mode 100644
index 0000000..f8f5bec
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/BUILD
@@ -0,0 +1,31 @@
+# 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
+
+load("//build_tools/bazel:iree_lit_test.bzl", "iree_lit_test_suite")
+load("//build_tools/bazel:enforce_glob.bzl", "enforce_glob")
+
+package(
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_lit_test_suite(
+    name = "lit",
+    srcs = enforce_glob(
+        [
+            "cmd_ops.mlir",
+            "resource_ops.mlir",
+            "timepoint_ops.mlir",
+            "transfer_ops.mlir",
+        ],
+        include = ["*.mlir"],
+    ),
+    cfg = "//compiler:lit.cfg.py",
+    tools = [
+        "//tools:iree-opt",
+        "@llvm-project//llvm:FileCheck",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/CMakeLists.txt
new file mode 100644
index 0000000..2b2b6c5
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/CMakeLists.txt
@@ -0,0 +1,26 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/BUILD#
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_lit_test_suite(
+  NAME
+    lit
+  SRCS
+    "cmd_ops.mlir"
+    "resource_ops.mlir"
+    "timepoint_ops.mlir"
+    "transfer_ops.mlir"
+  TOOLS
+    FileCheck
+    iree-opt
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/cmd_ops.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/cmd_ops.mlir
new file mode 100644
index 0000000..3058613
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/cmd_ops.mlir
@@ -0,0 +1,131 @@
+// RUN: iree-opt --split-input-file --iree-hal-inline-conversion %s | FileCheck %s
+
+// NOTE: memory control ops are currently ignored as we're executing inline and
+// assume coherent memory.
+
+// CHECK-LABEL: @cmdMemoryControl
+func.func @cmdMemoryControl(%arg0: !stream.resource<transient>, %arg1: index) -> !stream.timepoint {
+  %c0 = arith.constant 0 : index
+  %c128 = arith.constant 128 : index
+  %fence = stream.cmd.execute with(%arg0 as %arg2: !stream.resource<transient>{%arg1}) {
+    stream.cmd.flush %arg2[%c0 for %c128] : !stream.resource<transient>{%arg1}
+    stream.cmd.invalidate %arg2[%c0 for %c128] : !stream.resource<transient>{%arg1}
+    stream.cmd.discard %arg2[%c0 for %c128] : !stream.resource<transient>{%arg1}
+  } => !stream.timepoint
+  // CHECK: return %c0
+  return %fence : !stream.timepoint
+}
+
+// -----
+
+// CHECK-LABEL: @cmdFill
+// CHECK-SAME: (%[[TARGET:.+]]: !util.buffer, %[[TARGET_SIZE:.+]]: index)
+func.func @cmdFill(%target: !stream.resource<transient>, %target_size: index) -> !stream.timepoint {
+  %c0 = arith.constant 0 : index
+  // CHECK-DAG: %[[LENGTH:.+]] = arith.constant 128
+  %length = arith.constant 128 : index
+  // CHECK-DAG: %[[VALUE:.+]] = arith.constant 255
+  %value = arith.constant 255 : i32
+  %fence = stream.cmd.execute with(%target as %target_inner: !stream.resource<transient>{%target_size}) {
+    // CHECK: util.buffer.fill %[[VALUE]], %[[TARGET]][%c0 for %[[LENGTH]]] : i32 -> !util.buffer{%[[TARGET_SIZE]]}
+    stream.cmd.fill %value, %target_inner[%c0 for %length] : i32 -> !stream.resource<transient>{%target_size}
+  } => !stream.timepoint
+  // CHECK: return %c0
+  return %fence : !stream.timepoint
+}
+
+// -----
+
+// CHECK-LABEL: @cmdCopy
+// CHECK-SAME: (%[[SRC:.+]]: !util.buffer, %[[SRC_SIZE:.+]]: index, %[[DST:.+]]: !util.buffer, %[[DST_SIZE:.+]]: index)
+func.func @cmdCopy(%src: !stream.resource<transient>, %src_size: index,
+                   %dst: !stream.resource<staging>, %dst_size: index) -> !stream.timepoint {
+  // CHECK-DAG: %[[SRC_OFFSET:.+]] = arith.constant 100
+  %src_offset = arith.constant 100 : index
+  // CHECK-DAG: %[[DST_OFFSET:.+]] = arith.constant 200
+  %dst_offset = arith.constant 200 : index
+  // CHECK-DAG: %[[LENGTH:.+]] = arith.constant 128
+  %length = arith.constant 128 : index
+  %fence = stream.cmd.execute with(%src as %src_inner: !stream.resource<transient>{%src_size},
+                                   %dst as %dst_inner: !stream.resource<staging>{%dst_size}) {
+    // CHECK: util.buffer.copy %[[SRC]][%[[SRC_OFFSET]]], %[[DST]][%[[DST_OFFSET]]], %[[LENGTH]] : !util.buffer{%[[SRC_SIZE]]} -> !util.buffer{%[[DST_SIZE]]}
+    stream.cmd.copy %src_inner[%src_offset], %dst_inner[%dst_offset], %length : !stream.resource<transient>{%src_size} -> !stream.resource<staging>{%dst_size}
+  } => !stream.timepoint
+  // CHECK: return %c0
+  return %fence : !stream.timepoint
+}
+
+// -----
+
+// CHECK-LABEL: @cmdExecute
+func.func @cmdExecute(%arg0: !stream.resource<transient>, %arg1: index, %arg2: !stream.resource<staging>, %arg3: index, %arg4: !stream.timepoint) -> !stream.timepoint {
+  %c0 = arith.constant 0 : index
+  %c128 = arith.constant 128 : index
+  %fence = stream.cmd.execute await(%arg4) => with(%arg0 as %arg5: !stream.resource<transient>{%arg1}, %arg2 as %arg6: !stream.resource<staging>{%arg3}) {
+    stream.cmd.concurrent {
+      // CHECK: util.buffer.copy
+      stream.cmd.copy %arg5[%c0], %arg6[%c0], %c128 : !stream.resource<transient>{%arg1} -> !stream.resource<staging>{%arg3}
+      // CHECK: util.buffer.copy
+      stream.cmd.copy %arg5[%c0], %arg6[%c0], %c128 : !stream.resource<transient>{%arg1} -> !stream.resource<staging>{%arg3}
+      stream.cmd.serial {
+        // CHECK: util.buffer.copy
+        stream.cmd.copy %arg5[%c0], %arg6[%c0], %c128 : !stream.resource<transient>{%arg1} -> !stream.resource<staging>{%arg3}
+        // CHECK: util.buffer.copy
+        stream.cmd.copy %arg5[%c0], %arg6[%c0], %c128 : !stream.resource<transient>{%arg1} -> !stream.resource<staging>{%arg3}
+      }
+      // CHECK: util.buffer.copy
+      stream.cmd.copy %arg5[%c0], %arg6[%c0], %c128 : !stream.resource<transient>{%arg1} -> !stream.resource<staging>{%arg3}
+    }
+  } => !stream.timepoint
+  // CHECK: return %c0
+  return %fence : !stream.timepoint
+}
+
+// -----
+
+// Provided by the iree-hal-inline-executables pass:
+func.func private @__dispatch_ex_dispatch(
+    index, index,                 // workload[2]
+    i32, i32,                     // push_constants[2]
+    !util.buffer, !util.buffer,   // bindingBuffers[2]
+    index, index,                 // bindingOffsets[2]
+    index, index)                 // bindingLengths[2]
+
+// NOTE: %buffer0 is transient and will map to a !util.buffer, while
+//       %buffer1 is external and will map to a !hal.buffer.
+
+// CHECK-LABEL: @cmdDispatch
+// CHECK-SAME: (%[[BUFFER0:.+]]: !util.buffer, %[[BUFFER0_SIZE:.+]]: index,
+// CHECK-SAME:  %[[BUFFER1:.+]]: !hal.buffer, %[[BUFFER1_SIZE:.+]]: index)
+func.func @cmdDispatch(%buffer0: !stream.resource<transient>, %buffer0_size: index,
+                       %buffer1: !stream.resource<external>, %buffer1_size: index) -> !stream.timepoint {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  %c4_i32 = arith.constant 4 : i32
+  %c5_i32 = arith.constant 5 : i32
+  %c128 = arith.constant 128 : index
+  // CHECK: %[[BUFFER0_REL_OFFSET:.+]] = arith.constant 200
+  %buffer0_offset = arith.constant 200 : index
+  // CHECK: %[[BUFFER1_REL_OFFSET:.+]] = arith.constant 300
+  %buffer1_offset = arith.constant 300 : index
+  %fence = stream.cmd.execute with(%buffer0 as %buffer0_inner: !stream.resource<transient>{%buffer0_size},
+                                   %buffer1 as %buffer1_inner: !stream.resource<external>{%buffer1_size}) {
+    // CHECK: %[[BUFFER1_STORAGE:.+]] = hal_inline.buffer.storage<%[[BUFFER1]]
+    // CHECK: call @__dispatch_ex_dispatch(
+    // CHECK-SAME: %c1, %c2,
+    // CHECK-SAME: %c4_i32, %c5_i32,
+    // CHECK-SAME: %[[BUFFER0]], %[[BUFFER1_STORAGE]],
+    // CHECK-SAME: %[[BUFFER0_REL_OFFSET]], %[[BUFFER1_REL_OFFSET]],
+    // CHECK-SAME: %c128, %c128)
+    stream.cmd.dispatch @ex::@dispatch[%c1, %c2](%c4_i32, %c5_i32 : i32, i32) {
+      ro %buffer0_inner[%buffer0_offset for %c128] : !stream.resource<transient>{%buffer0_size},
+      wo %buffer1_inner[%buffer1_offset for %c128] : !stream.resource<external>{%buffer1_size}
+    } attributes {
+      // From the iree-hal-inline-executables pass:
+      hal_inline.target = @__dispatch_ex_dispatch
+    }
+  } => !stream.timepoint
+  // CHECK: return %c0
+  return %fence : !stream.timepoint
+}
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/resource_ops.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/resource_ops.mlir
new file mode 100644
index 0000000..1d18138
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/resource_ops.mlir
@@ -0,0 +1,137 @@
+// RUN: iree-opt --split-input-file --iree-hal-inline-conversion %s | FileCheck %s
+
+// CHECK-LABEL: @resourceAlloc
+// CHECK-SAME: (%[[LENGTH:.+]]: index)
+func.func @resourceAlloc(%length: index) -> !stream.resource<transient> {
+  // CHECK: %[[BUFFER:.+]], %[[STORAGE:.+]] = hal_inline.buffer.allocate alignment(%c64) : !hal.buffer{%[[LENGTH]]}
+  %result = stream.resource.alloc uninitialized : !stream.resource<transient>{%length}
+  // CHECK: return %[[STORAGE]]
+  return %result : !stream.resource<transient>
+}
+
+// -----
+
+// CHECK-LABEL: @resourceAlloca
+// CHECK-SAME: (%[[LENGTH:.+]]: index)
+func.func @resourceAlloca(%length: index) -> (!stream.resource<staging>, !stream.timepoint) {
+  // CHECK: %[[BUFFER:.+]], %[[STORAGE:.+]] = hal_inline.buffer.allocate alignment(%c64) : !hal.buffer{%[[LENGTH]]}
+  %0:2 = stream.resource.alloca uninitialized : !stream.resource<staging>{%length} => !stream.timepoint
+  // CHECK: %[[IMMEDIATE:.+]] = arith.constant 0 : i64
+  // CHECK: return %[[STORAGE]], %[[IMMEDIATE]]
+  return %0#0, %0#1 : !stream.resource<staging>, !stream.timepoint
+}
+
+// -----
+
+// CHECK-LABEL: @resourceAllocaAwait
+// CHECK-SAME: (%[[LENGTH:.+]]: index, %[[TIMEPOINT:.+]]: i64)
+func.func @resourceAllocaAwait(%length: index, %await_timepoint: !stream.timepoint) -> (!stream.resource<staging>, !stream.timepoint) {
+  // CHECK: %[[BUFFER:.+]], %[[STORAGE:.+]] = hal_inline.buffer.allocate alignment(%c64) : !hal.buffer{%[[LENGTH]]}
+  %0:2 = stream.resource.alloca uninitialized await(%await_timepoint) => !stream.resource<staging>{%length} => !stream.timepoint
+  // CHECK: %[[IMMEDIATE:.+]] = arith.constant 0 : i64
+  // CHECK: return %[[STORAGE]], %[[IMMEDIATE]]
+  return %0#0, %0#1 : !stream.resource<staging>, !stream.timepoint
+}
+
+// -----
+
+// NOTE: we don't do anything with deallocs today but could add a discard op.
+
+// CHECK-LABEL: @resourceDealloca
+func.func @resourceDealloca(%arg0: index, %arg1: !stream.resource<staging>, %arg2: !stream.timepoint) -> !stream.timepoint {
+  %0 = stream.resource.dealloca %arg1 : !stream.resource<staging>{%arg0} => !stream.timepoint
+  // CHECK: %[[IMMEDIATE:.+]] = arith.constant 0 : i64
+  // CHECK: return %[[IMMEDIATE]]
+  return %0 : !stream.timepoint
+}
+
+// -----
+
+// NOTE: we don't do anything with deallocs today but could add a discard op.
+
+// CHECK-LABEL: @resourceDeallocaAwait
+func.func @resourceDeallocaAwait(%arg0: index, %arg1: !stream.resource<staging>, %arg2: !stream.timepoint) -> !stream.timepoint {
+  %0 = stream.resource.dealloca await(%arg2) => %arg1 : !stream.resource<staging>{%arg0} => !stream.timepoint
+  // CHECK: %[[IMMEDIATE:.+]] = arith.constant 0 : i64
+  // CHECK: return %[[IMMEDIATE]]
+  return %0 : !stream.timepoint
+}
+
+// -----
+
+// CHECK-LABEL: @resourceSize
+func.func @resourceSize(%arg0: !stream.resource<transient>) -> index {
+  // CHECK: %[[SIZE:.+]] = util.buffer.size %arg0
+  %0 = stream.resource.size %arg0 : !stream.resource<transient>
+  // CHECK: return %[[SIZE]]
+  return %0 : index
+}
+
+// -----
+
+// CHECK-LABEL: @resourceMap
+// CHECK-SAME: (%[[SOURCE:.+]]: !util.buffer)
+func.func @resourceMap(%source: !util.buffer) -> !stream.resource<staging> {
+  // CHECK-DAG: %[[OFFSET:.+]] = arith.constant 100
+  %offset = arith.constant 100 : index
+  // CHECK-DAG: %[[LENGTH:.+]] = arith.constant 128
+  %length = arith.constant 128 : index
+  // CHECK: %[[SOURCE_SIZE:.+]] = util.buffer.size %[[SOURCE]] : !util.buffer
+  // CHECK: %[[MAPPING:.+]] = util.buffer.subspan %[[SOURCE]][%[[OFFSET]]] : !util.buffer{%[[SOURCE_SIZE]]} -> !util.buffer{%[[LENGTH]]}
+  %mapping = stream.resource.map %source[%offset] : !util.buffer -> !stream.resource<staging>{%length}
+  // CHECK: return %[[MAPPING]]
+  return %mapping : !stream.resource<staging>
+}
+
+// -----
+
+// CHECK-LABEL: @resourceTryMap
+// CHECK-SAME: (%[[SOURCE:.+]]: !util.buffer)
+func.func @resourceTryMap(%source: !util.buffer) -> (i1, !stream.resource<constant>) {
+  // CHECK-DAG: %[[OFFSET:.+]] = arith.constant 100
+  %offset = arith.constant 100 : index
+  // CHECK-DAG: %[[LENGTH:.+]] = arith.constant 128
+  %length = arith.constant 128 : index
+  // CHECK: %[[SOURCE_SIZE:.+]] = util.buffer.size %[[SOURCE]] : !util.buffer
+  // CHECK: %[[MAPPING:.+]] = util.buffer.subspan %[[SOURCE]][%[[OFFSET]]] : !util.buffer{%[[SOURCE_SIZE]]} -> !util.buffer{%[[LENGTH]]}
+  // CHECK-DAG: %[[DID_MAP:.+]] = arith.constant true
+  %did_map, %mapping = stream.resource.try_map %source[%offset] : !util.buffer -> i1, !stream.resource<constant>{%length}
+  // CHECK: return %[[DID_MAP]], %[[MAPPING]]
+  return %did_map, %mapping : i1, !stream.resource<constant>
+}
+
+// -----
+
+// CHECK-LABEL: @resourceLoad
+// CHECK-SAME: (%[[BUFFER:.+]]: !util.buffer, %[[BUFFER_SIZE:.+]]: index, %[[OFFSET:.+]]: index)
+func.func @resourceLoad(%resource: !stream.resource<staging>, %resource_size: index, %offset: index) -> i32 {
+  // CHECK: %[[VALUE:.+]] = util.buffer.load %[[BUFFER]][%[[OFFSET]]] : !util.buffer{%[[BUFFER_SIZE]]} -> i32
+  %0 = stream.resource.load %resource[%offset] : !stream.resource<staging>{%resource_size} -> i32
+  // CHECK: return %[[VALUE]]
+  return %0 : i32
+}
+
+// -----
+
+// CHECK-LABEL: @resourceStore
+// CHECK-SAME: (%[[BUFFER:.+]]: !util.buffer, %[[BUFFER_SIZE:.+]]: index, %[[OFFSET:.+]]: index)
+func.func @resourceStore(%resource: !stream.resource<staging>, %resource_size: index, %offset: index) {
+  // CHECK-DAG: %[[VALUE:.+]] = arith.constant 123
+  %value = arith.constant 123 : i32
+  // CHECK: util.buffer.store %[[VALUE]], %[[BUFFER]][%[[OFFSET]]] : i32 -> !util.buffer{%[[BUFFER_SIZE]]}
+  stream.resource.store %value, %resource[%offset] : i32 -> !stream.resource<staging>{%resource_size}
+  return
+}
+
+// -----
+
+// CHECK-LABEL: @resourceSubview
+// CHECK-SAME: (%[[BUFFER:.+]]: !util.buffer, %[[BUFFER_SIZE:.+]]: index)
+func.func @resourceSubview(%resource: !stream.resource<transient>, %resource_size: index) -> !stream.resource<transient> {
+  %c128 = arith.constant 128 : index
+  %c256 = arith.constant 256 : index
+  // CHECK: %[[SUBSPAN:.+]] = util.buffer.subspan %[[BUFFER]][%c128] : !util.buffer{%[[BUFFER_SIZE]]} -> !util.buffer{%c256}
+  %0 = stream.resource.subview %resource[%c128] : !stream.resource<transient>{%resource_size} -> !stream.resource<transient>{%c256}
+  // CHECK: return %[[SUBSPAN]]
+  return %0 : !stream.resource<transient>
+}
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/timepoint_ops.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/timepoint_ops.mlir
new file mode 100644
index 0000000..aef6a09
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/timepoint_ops.mlir
@@ -0,0 +1,48 @@
+// RUN: iree-opt --split-input-file --iree-hal-inline-conversion %s | FileCheck %s
+
+// NOTE: the inline HAL doesn't model timepoints and we just turn them into ints
+// that'll eventually get DCE'd.
+
+// CHECK-LABEL: @rwTimepoint
+// CHECK-SAME: = 0 : i64
+util.global private mutable @rwTimepoint = #stream.timepoint<immediate>
+// CHECK: func.func @globalTimepoint(%arg0: i64) -> i64
+func.func @globalTimepoint(%arg0: !stream.timepoint) -> !stream.timepoint {
+  // CHECK: util.global.store %arg0, @rwTimepoint
+  util.global.store %arg0, @rwTimepoint : !stream.timepoint
+  // CHECK: %[[VALUE:.+]] = util.global.load @rwTimepoint
+  %value = util.global.load @rwTimepoint : !stream.timepoint
+  // CHECK: return %[[VALUE]]
+  return %value : !stream.timepoint
+}
+
+// -----
+
+// CHECK-LABEL: @timepointImmediate
+func.func @timepointImmediate() -> !stream.timepoint {
+  // CHECK: %[[TIMEPOINT:.+]] = arith.constant 0
+  %0 = stream.timepoint.immediate => !stream.timepoint
+  // CHECK: return %[[TIMEPOINT]]
+  return %0 : !stream.timepoint
+}
+
+// -----
+
+// CHECK-LABEL: @timepointJoin
+func.func @timepointJoin(%arg0: !stream.timepoint, %arg1: !stream.timepoint) -> !stream.timepoint {
+  // CHECK: %[[TIMEPOINT:.+]] = arith.constant 0
+  %0 = stream.timepoint.join max(%arg0, %arg1) => !stream.timepoint
+  // CHECK: return %[[TIMEPOINT]]
+  return %0 : !stream.timepoint
+}
+
+// -----
+
+// CHECK-LABEL: @timepointAwait
+func.func @timepointAwait(%arg0: !stream.timepoint, %arg1: !stream.resource<staging>, %arg2: !stream.resource<*>) -> (!stream.resource<staging>, !stream.resource<*>) {
+  %c100 = arith.constant 100 : index
+  %c200 = arith.constant 200 : index
+  %0:2 = stream.timepoint.await %arg0 => %arg1, %arg2 : !stream.resource<staging>{%c100}, !stream.resource<*>{%c200}
+  // CHECK: return %arg1, %arg2
+  return %0#0, %0#1 : !stream.resource<staging>, !stream.resource<*>
+}
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/transfer_ops.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/transfer_ops.mlir
new file mode 100644
index 0000000..2bbd473
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/test/transfer_ops.mlir
@@ -0,0 +1,49 @@
+// RUN: iree-opt --split-input-file --iree-hal-inline-conversion %s | FileCheck %s
+
+// CHECK-LABEL: @tensorImportBuffer
+// CHECK-SAME: (%[[BUFFER:.+]]: !hal.buffer, %[[RESOURCE_SIZE:.+]]: index, %[[DIM:.+]]: index) -> !hal.buffer
+func.func @tensorImportBuffer(%buffer: !hal.buffer, %resource_size: index, %dim: index) -> !stream.resource<external> {
+  %0 = stream.tensor.import %buffer : !hal.buffer -> tensor<?x5xf32>{%dim} in !stream.resource<external>{%resource_size}
+  // CHECK: return %[[BUFFER]]
+  return %0 : !stream.resource<external>
+}
+
+// -----
+
+// NOTE: buffer view metadata assertions via hal.buffer_view.assert are added
+// when lowering into the stream dialect; here we only care about the storage
+// buffer itself.
+
+// CHECK-LABEL: @tensorImportBufferView
+// CHECK-SAME: (%[[BUFFER_VIEW:.+]]: !hal.buffer_view, %[[RESOURCE_SIZE:.+]]: index, %[[DIM:.+]]: index) -> !hal.buffer
+func.func @tensorImportBufferView(%buffer_view: !hal.buffer_view, %resource_size: index, %dim: index) -> !stream.resource<external> {
+  // CHECK: %[[BUFFER:.+]] = hal_inline.buffer_view.buffer<%[[BUFFER_VIEW]] : !hal.buffer_view> : !hal.buffer
+  %0 = stream.tensor.import %buffer_view : !hal.buffer_view -> tensor<?x5xf32>{%dim} in !stream.resource<external>{%resource_size}
+  // CHECK: return %[[BUFFER]]
+  return %0 : !stream.resource<external>
+}
+
+// -----
+
+// CHECK-LABEL: @tensorExportBuffer
+// CHECK-SAME: (%[[BUFFER:.+]]: !hal.buffer, %[[RESOURCE_SIZE:.+]]: index, %[[DIM:.+]]: index) -> !hal.buffer
+func.func @tensorExportBuffer(%resource: !stream.resource<external>, %resource_size: index, %dim: index) -> !hal.buffer {
+  %0 = stream.tensor.export %resource : tensor<?x1x10xf32>{%dim} in !stream.resource<external>{%resource_size} -> !hal.buffer
+  // CHECK: return %[[BUFFER]]
+  return %0 : !hal.buffer
+}
+
+// -----
+
+// CHECK-LABEL: @tensorExportBufferView
+// CHECK-SAME: (%[[BUFFER:.+]]: !hal.buffer, %[[RESOURCE_SIZE:.+]]: index, %[[DIM:.+]]: index) -> !hal.buffer
+func.func @tensorExportBufferView(%resource: !stream.resource<external>, %resource_size: index, %dim: index) -> !hal.buffer_view {
+  // CHECK: %[[BUFFER_VIEW:.+]] = hal_inline.buffer_view.create
+  // CHECK-SAME: buffer(%[[BUFFER]] : !hal.buffer)
+  // CHECK-SAME: shape([%[[DIM]], %c1, %c10])
+  // CHECK-SAME: type(%c553648160_i32)
+  // CHECK-SAME: encoding(%c1_i32)
+  %0 = stream.tensor.export %resource : tensor<?x1x10xf32>{%dim} in !stream.resource<external>{%resource_size} -> !hal.buffer_view
+  // CHECK: return %[[BUFFER_VIEW]]
+  return %0 : !hal.buffer_view
+}
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/BUILD
new file mode 100644
index 0000000..9cedf21
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/BUILD
@@ -0,0 +1,114 @@
+# 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
+
+load("@llvm-project//mlir:tblgen.bzl", "td_library")
+load("//build_tools/bazel:build_defs.oss.bzl", "iree_compiler_cc_library")
+load("//build_tools/bazel:enforce_glob.bzl", "enforce_glob")
+load("//build_tools/bazel:iree_tablegen.bzl", "iree_gentbl_cc_library", "iree_tablegen_doc")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+exports_files(["HALInlineOps.td"])
+
+td_library(
+    name = "td_files",
+    srcs = enforce_glob(
+        [
+            "HALInlineBase.td",
+            "HALInlineOps.td",
+        ],
+        include = ["*.td"],
+    ),
+    deps = [
+        "//compiler/src/iree/compiler/Dialect/HAL/IR:td_files",
+        "//compiler/src/iree/compiler/Dialect/Util/IR:td_files",
+        "@llvm-project//mlir:FuncTdFiles",
+        "@llvm-project//mlir:OpBaseTdFiles",
+    ],
+)
+
+iree_compiler_cc_library(
+    name = "IR",
+    srcs = [
+        "HALInlineOps.cpp",
+    ],
+    hdrs = [
+        "HALInlineOps.h",
+        "HALInlineOps.h.inc",
+    ],
+    textual_hdrs = [
+        "HALInlineOps.cpp.inc",
+    ],
+    deps = [
+        ":HALInlineOpsGen",
+        "//compiler/src/iree/compiler/Dialect/HAL/IR",
+        "//compiler/src/iree/compiler/Dialect/Util/IR",
+        "//compiler/src/iree/compiler/Dialect/VM/IR",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//mlir:ArithmeticDialect",
+        "@llvm-project//mlir:FuncDialect",
+        "@llvm-project//mlir:IR",
+        "@llvm-project//mlir:SideEffectInterfaces",
+        "@llvm-project//mlir:Support",
+        "@llvm-project//mlir:TransformUtils",
+        "@llvm-project//mlir:TranslateLib",
+    ],
+)
+
+iree_compiler_cc_library(
+    name = "HALInlineDialect",
+    srcs = ["HALInlineDialect.cpp"],
+    hdrs = ["HALInlineDialect.h"],
+    deps = [
+        ":IR",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline:hal_inline_imports",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM",
+        "//compiler/src/iree/compiler/Dialect/VM/Conversion",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//mlir:FuncDialect",
+        "@llvm-project//mlir:IR",
+        "@llvm-project//mlir:Parser",
+        "@llvm-project//mlir:Support",
+        "@llvm-project//mlir:TransformUtils",
+    ],
+)
+
+iree_gentbl_cc_library(
+    name = "HALInlineOpsGen",
+    tbl_outs = [
+        (
+            ["--gen-op-decls"],
+            "HALInlineOps.h.inc",
+        ),
+        (
+            ["--gen-op-defs"],
+            "HALInlineOps.cpp.inc",
+        ),
+    ],
+    tblgen = "@llvm-project//mlir:mlir-tblgen",
+    td_file = "HALInlineOps.td",
+    deps = [":td_files"],
+)
+
+iree_tablegen_doc(
+    name = "HALInlineDialecDocGen",
+    tbl_outs = [
+        (
+            [
+                "--dialect=hal_inline",
+                "--gen-dialect-doc",
+            ],
+            "HALInlineDialect.md",
+        ),
+    ],
+    tblgen = "@llvm-project//mlir:mlir-tblgen",
+    td_file = "HALInlineOps.td",
+    deps = [":td_files"],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/CMakeLists.txt
new file mode 100644
index 0000000..04ba81e
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/CMakeLists.txt
@@ -0,0 +1,79 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/BUILD               #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_cc_library(
+  NAME
+    IR
+  HDRS
+    "HALInlineOps.h"
+    "HALInlineOps.h.inc"
+  TEXTUAL_HDRS
+    "HALInlineOps.cpp.inc"
+  SRCS
+    "HALInlineOps.cpp"
+  DEPS
+    ::HALInlineOpsGen
+    LLVMSupport
+    MLIRArithmeticDialect
+    MLIRFuncDialect
+    MLIRIR
+    MLIRSideEffectInterfaces
+    MLIRSupport
+    MLIRTransformUtils
+    MLIRTranslateLib
+    iree::compiler::Dialect::HAL::IR
+    iree::compiler::Dialect::Util::IR
+    iree::compiler::Dialect::VM::IR
+  PUBLIC
+)
+
+iree_cc_library(
+  NAME
+    HALInlineDialect
+  HDRS
+    "HALInlineDialect.h"
+  SRCS
+    "HALInlineDialect.cpp"
+  DEPS
+    ::IR
+    LLVMSupport
+    MLIRFuncDialect
+    MLIRIR
+    MLIRParser
+    MLIRSupport
+    MLIRTransformUtils
+    iree::compiler::Dialect::Modules::HAL::Inline::Conversion::HALInlineToVM
+    iree::compiler::Dialect::Modules::HAL::Inline::hal_inline_imports
+    iree::compiler::Dialect::VM::Conversion
+  PUBLIC
+)
+
+iree_tablegen_library(
+  NAME
+    HALInlineOpsGen
+  TD_FILE
+    "HALInlineOps.td"
+  OUTS
+    --gen-op-decls HALInlineOps.h.inc
+    --gen-op-defs HALInlineOps.cpp.inc
+)
+
+iree_tablegen_doc(
+  NAME
+    HALInlineDialecDocGen
+  TD_FILE
+    "HALInlineOps.td"
+  OUTS
+    --dialect=hal_inline --gen-dialect-doc HALInlineDialect.md
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineBase.td b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineBase.td
new file mode 100644
index 0000000..c8bb807
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineBase.td
@@ -0,0 +1,44 @@
+// 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_DIALECT_MODULES_HAL_INLINE_BASE
+#define IREE_DIALECT_MODULES_HAL_INLINE_BASE
+
+include "iree/compiler/Dialect/Util/IR/UtilBase.td"
+
+//===----------------------------------------------------------------------===//
+// IREE HAL inline dialect
+//===----------------------------------------------------------------------===//
+
+def HALInline_Dialect : Dialect {
+  let name = "hal_inline";
+  let cppNamespace = "::mlir::iree_compiler::IREE::HAL::Inline";
+  let emitAccessorPrefix = kEmitAccessorPrefix_Prefixed;
+
+  let summary = [{
+    IREE inline HAL interop runtime module dialect.
+  }];
+  let description = [{
+    Low-level dialect for limited in-process ABI interop with the full HAL.
+    Only operates synchronously, single-threaded, and on host-local buffers. Use
+    the full HAL for all other cases.
+
+    This dialect can be used alongside the full HAL but is intended for use in
+    standalone configurations or paired with the `hal_loader` dialect which also
+    carries the same usage restrictions.
+
+    See `hal_inline.imports.mlir` for the full list of exported functions.
+  }];
+}
+
+//===----------------------------------------------------------------------===//
+// Base HALInline op classes
+//===----------------------------------------------------------------------===//
+
+class HALInline_Op<string mnemonic, list<Trait> traits = []> :
+    Op<HALInline_Dialect, mnemonic, !listconcat(traits, [])> {}
+
+#endif  // IREE_DIALECT_MODULES_HAL_INLINE_BASE
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.cpp b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.cpp
new file mode 100644
index 0000000..85fec80
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.cpp
@@ -0,0 +1,63 @@
+// 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/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.h"
+
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALInlineToVM/ConvertHALInlineToVM.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/hal_inline.imports.h"
+#include "iree/compiler/Dialect/VM/Conversion/ConversionDialectInterface.h"
+#include "llvm/Support/SourceMgr.h"
+#include "mlir/IR/DialectImplementation.h"
+#include "mlir/IR/OpImplementation.h"
+#include "mlir/Parser/Parser.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+namespace Inline {
+
+namespace {
+
+class HALInlineToVMConversionInterface : public VMConversionDialectInterface {
+ public:
+  using VMConversionDialectInterface::VMConversionDialectInterface;
+
+  OwningOpRef<mlir::ModuleOp> parseVMImportModule() const override {
+    return mlir::parseSourceString<mlir::ModuleOp>(
+        StringRef(iree_hal_inline_imports_create()->data,
+                  iree_hal_inline_imports_create()->size),
+        getDialect()->getContext());
+  }
+
+  void populateVMConversionPatterns(
+      SymbolTable &importSymbols, RewritePatternSet &patterns,
+      ConversionTarget &conversionTarget,
+      TypeConverter &typeConverter) const override {
+    conversionTarget.addIllegalDialect<IREE::HAL::Inline::HALInlineDialect>();
+    populateHALInlineToVMPatterns(getDialect()->getContext(), conversionTarget,
+                                  typeConverter, importSymbols, patterns);
+  }
+};
+
+}  // namespace
+
+HALInlineDialect::HALInlineDialect(MLIRContext *context)
+    : Dialect(getDialectNamespace(), context, TypeID::get<HALInlineDialect>()) {
+  addInterfaces<HALInlineToVMConversionInterface>();
+
+#define GET_OP_LIST
+  addOperations<
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.cpp.inc"
+      >();
+}
+
+}  // namespace Inline
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.h b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.h
new file mode 100644
index 0000000..b53b70d
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.h
@@ -0,0 +1,31 @@
+// 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_DIALECT_MODULES_HAL_INLINE_IR_HALINLINEDIALECT_H_
+#define IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_IR_HALINLINEDIALECT_H_
+
+#include "mlir/IR/Dialect.h"
+#include "mlir/IR/OpDefinition.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+namespace Inline {
+
+class HALInlineDialect : public Dialect {
+ public:
+  explicit HALInlineDialect(MLIRContext *context);
+  static StringRef getDialectNamespace() { return "hal_inline"; }
+};
+
+}  // namespace Inline
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_IR_HALINLINEDIALECT_H_
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.cpp b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.cpp
new file mode 100644
index 0000000..e8aa7e4
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.cpp
@@ -0,0 +1,211 @@
+// 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/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h"
+
+#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
+#include "iree/compiler/Dialect/Util/IR/UtilTypes.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/SMLoc.h"
+#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
+#include "mlir/IR/Attributes.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/OpImplementation.h"
+#include "mlir/IR/SymbolTable.h"
+#include "mlir/IR/TypeUtilities.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+namespace Inline {
+
+//===----------------------------------------------------------------------===//
+// hal_inline.buffer.allocate
+//===----------------------------------------------------------------------===//
+
+void BufferAllocateOp::getAsmResultNames(
+    function_ref<void(Value, StringRef)> setNameFn) {
+  setNameFn(getResult(), "buffer");
+  setNameFn(getStorage(), "storage");
+}
+
+Value BufferAllocateOp::getOperandSize(unsigned idx) { return {}; }
+
+Value BufferAllocateOp::getResultSize(unsigned idx) {
+  return getAllocationSize();
+}
+
+//===----------------------------------------------------------------------===//
+// hal_inline.buffer.allocate.initialized
+//===----------------------------------------------------------------------===//
+
+void BufferAllocateInitializedOp::getAsmResultNames(
+    function_ref<void(Value, StringRef)> setNameFn) {
+  setNameFn(getResult(), "buffer");
+  setNameFn(getStorage(), "storage");
+}
+
+Value BufferAllocateInitializedOp::getOperandSize(unsigned idx) { return {}; }
+
+Value BufferAllocateInitializedOp::getResultSize(unsigned idx) {
+  return getLength();
+}
+
+//===----------------------------------------------------------------------===//
+// hal_inline.buffer.wrap
+//===----------------------------------------------------------------------===//
+
+void BufferWrapOp::getAsmResultNames(
+    function_ref<void(Value, StringRef)> setNameFn) {
+  setNameFn(getResult(), "mapped");
+}
+
+Value BufferWrapOp::getOperandSize(unsigned idx) { return {}; }
+
+Value BufferWrapOp::getResultSize(unsigned idx) { return getLength(); }
+
+//===----------------------------------------------------------------------===//
+// hal_inline.buffer.subspan
+//===----------------------------------------------------------------------===//
+
+void BufferSubspanOp::getAsmResultNames(
+    function_ref<void(Value, StringRef)> setNameFn) {
+  setNameFn(getResult(), "buffer");
+}
+
+Value BufferSubspanOp::getOperandSize(unsigned idx) { return getLength(); }
+
+Value BufferSubspanOp::getResultSize(unsigned idx) { return getLength(); }
+
+//===----------------------------------------------------------------------===//
+// hal_inline.buffer.byte_length
+//===----------------------------------------------------------------------===//
+
+void BufferLengthOp::getAsmResultNames(
+    function_ref<void(Value, StringRef)> setNameFn) {
+  setNameFn(getResult(), "length");
+}
+
+OpFoldResult BufferLengthOp::fold(ArrayRef<Attribute> operands) {
+  Operation *op = this->getOperation();
+  return IREE::Util::SizeAwareTypeInterface::findSizeValue(
+      getBuffer(), op->getBlock(), Block::iterator(op));
+}
+
+//===----------------------------------------------------------------------===//
+// hal_inline.buffer.storage
+//===----------------------------------------------------------------------===//
+
+void BufferStorageOp::getAsmResultNames(
+    function_ref<void(Value, StringRef)> setNameFn) {
+  setNameFn(getResult(), "storage");
+}
+
+OpFoldResult BufferStorageOp::fold(ArrayRef<Attribute> operands) {
+  auto *definingOp = getBuffer().getDefiningOp();
+  if (!definingOp) return {};
+  if (auto sourceOp =
+          dyn_cast_or_null<IREE::HAL::Inline::BufferAllocateOp>(definingOp)) {
+    return sourceOp.getStorage();
+  } else if (auto sourceOp = dyn_cast_or_null<
+                 IREE::HAL::Inline::BufferAllocateInitializedOp>(definingOp)) {
+    return sourceOp.getStorage();
+  }
+  return {};
+}
+
+//===----------------------------------------------------------------------===//
+// hal_inline.buffer_view.create
+//===----------------------------------------------------------------------===//
+
+void BufferViewCreateOp::build(OpBuilder &builder, OperationState &state,
+                               Value buffer, int32_t elementType,
+                               int32_t encodingType, ValueRange shape) {
+  build(builder, state, buffer,
+        builder.createOrFold<arith::ConstantIntOp>(state.location, elementType,
+                                                   32),
+        builder.createOrFold<arith::ConstantIntOp>(state.location, encodingType,
+                                                   32),
+        shape);
+}
+
+void BufferViewCreateOp::build(OpBuilder &builder, OperationState &state,
+                               Value buffer, Value elementType,
+                               Value encodingType, ValueRange shape) {
+  state.addOperands({buffer, elementType, encodingType});
+  state.addOperands(shape);
+  state.addTypes({BufferViewType::get(builder.getContext())});
+}
+
+void BufferViewCreateOp::getAsmResultNames(
+    function_ref<void(Value, StringRef)> setNameFn) {
+  setNameFn(getResult(), "view");
+}
+
+//===----------------------------------------------------------------------===//
+// hal_inline.buffer_view.buffer
+//===----------------------------------------------------------------------===//
+
+void BufferViewBufferOp::getAsmResultNames(
+    function_ref<void(Value, StringRef)> setNameFn) {
+  setNameFn(getResult(), "buffer");
+}
+
+namespace {
+
+/// Skips a hal.buffer_view.buffer accessor when the buffer view was created in
+/// the same scope and we know the origin buffer.
+struct SkipBufferViewBufferOp : public OpRewritePattern<BufferViewBufferOp> {
+  using OpRewritePattern<BufferViewBufferOp>::OpRewritePattern;
+
+  LogicalResult matchAndRewrite(BufferViewBufferOp op,
+                                PatternRewriter &rewriter) const override {
+    if (auto createOp = dyn_cast_or_null<BufferViewCreateOp>(
+            op.getBufferView().getDefiningOp())) {
+      rewriter.replaceOp(op, createOp.getBuffer());
+      return success();
+    }
+    return failure();
+  }
+};
+
+}  // namespace
+
+void BufferViewBufferOp::getCanonicalizationPatterns(RewritePatternSet &results,
+                                                     MLIRContext *context) {
+  results.insert<SkipBufferViewBufferOp>(context);
+}
+
+//===----------------------------------------------------------------------===//
+// hal_inline.device.query
+//===----------------------------------------------------------------------===//
+
+LogicalResult DeviceQueryOp::verify() {
+  DeviceQueryOp op = *this;
+  if (op.getDefaultValue().has_value()) {
+    if (auto typedDefaultValue = op.getDefaultValue()->dyn_cast<TypedAttr>()) {
+      if (typedDefaultValue.getType() != op.getValue().getType()) {
+        return op.emitOpError()
+               << "type mismatch between result and default value";
+      }
+    }
+  }
+  return success();
+}
+
+}  // namespace Inline
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
+
+//===----------------------------------------------------------------------===//
+// TableGen definitions (intentionally last)
+//===----------------------------------------------------------------------===//
+
+#define GET_OP_CLASSES
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.cpp.inc"
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h
new file mode 100644
index 0000000..97af7e8
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h
@@ -0,0 +1,26 @@
+// 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_DIALECT_MODULES_HAL_INLINE_IR_HALINLINEOPS_H_
+#define IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_IR_HALINLINEOPS_H_
+
+#include <cstdint>
+
+#include "iree/compiler/Dialect/HAL/IR/HALTypes.h"
+#include "iree/compiler/Dialect/Util/IR/UtilTraits.h"
+#include "mlir/IR/Attributes.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Dialect.h"
+#include "mlir/IR/OpDefinition.h"
+#include "mlir/IR/OpImplementation.h"
+#include "mlir/IR/SymbolTable.h"
+#include "mlir/Interfaces/SideEffectInterfaces.h"
+
+#define GET_OP_CLASSES
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h.inc"  // IWYU pragma: keep
+
+#endif  // IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_IR_HALINLINEOPS_H_
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.td b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.td
new file mode 100644
index 0000000..134575f
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.td
@@ -0,0 +1,458 @@
+// 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_DIALECT_MODULES_HAL_INLINE_OPS
+#define IREE_DIALECT_MODULES_HAL_INLINE_OPS
+
+include "iree/compiler/Dialect/HAL/IR/HALBase.td"
+include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineBase.td"
+include "iree/compiler/Dialect/Util/IR/UtilAttrs.td"
+include "iree/compiler/Dialect/Util/IR/UtilInterfaces.td"
+include "mlir/IR/OpAsmInterface.td"
+include "mlir/Interfaces/SideEffectInterfaces.td"
+
+class HALInline_PureOp<string mnemonic, list<Trait> traits = []> :
+    HALInline_Op<mnemonic, !listconcat(traits, [NoSideEffect])>;
+
+//===----------------------------------------------------------------------===//
+// !hal.buffer / iree_hal_buffer_t
+//===----------------------------------------------------------------------===//
+
+def HALInline_BufferAllocateOp : HALInline_Op<"buffer.allocate", [
+  DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
+  DeclareOpInterfaceMethods<Util_SizeAwareOp>,
+]> {
+  let summary = [{empty buffer allocation operation}];
+  let description = [{
+    Allocates a buffer of the given size.
+    The size of the buffer returned may be larger than the requested size if the
+    allocator has specific alignment requirements or minimum allocation sizes.
+  }];
+
+  let arguments = (ins
+    HAL_DeviceSize:$minimum_alignment,
+    HAL_DeviceSize:$allocation_size
+  );
+  let results = (outs
+    HAL_Buffer:$result,
+    Util_BufferType:$storage
+  );
+
+  let assemblyFormat = [{
+    `alignment` `(` $minimum_alignment `)`
+    `:` custom<SizeAwareType>(type($result), $allocation_size) `in` type($storage)
+    attr-dict-with-keyword
+  }];
+}
+
+def HALInline_BufferAllocateInitializedOp : HALInline_Op<"buffer.allocate.initialized", [
+  DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
+  DeclareOpInterfaceMethods<Util_SizeAwareOp>,
+]> {
+  let summary = [{buffer allocation with cloning}];
+  let description = [{
+    Allocates a buffer with a copy of the provided contents.
+  }];
+
+  let arguments = (ins
+    HAL_DeviceSize:$minimum_alignment,
+    Util_BufferType:$source,
+    HAL_DeviceSize:$offset,
+    HAL_DeviceSize:$length
+  );
+  let results = (outs
+    HAL_Buffer:$result,
+    Util_BufferType:$storage
+  );
+
+  let assemblyFormat = [{
+    `source` `(` $source `:` type($source) `)` `` `[` $offset `,` $length `]`
+    `alignment` `(` $minimum_alignment `)`
+    `:` custom<SizeAwareType>(type($result), ref($length)) `in` type($storage)
+    attr-dict-with-keyword
+  }];
+}
+
+def HALInline_BufferWrapOp : HALInline_Op<"buffer.wrap", [
+  DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
+  DeclareOpInterfaceMethods<Util_SizeAwareOp>,
+]> {
+  let summary = [{host buffer wrapping operation}];
+  let description = [{
+    Tries wrapping a !hal.buffer around host memory backed by the given byte
+    buffer.
+  }];
+
+  let arguments = (ins
+    Util_BufferType:$source,
+    HAL_DeviceSize:$offset,
+    HAL_DeviceSize:$length
+  );
+  let results = (outs
+    HAL_Buffer:$result
+  );
+
+  // TODO(benvanik): change type/usage to ref params.
+  let assemblyFormat = [{
+    `source` `(` $source `:` type($source) `)` `` `[` $offset `,` $length `]`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+}
+
+def HALInline_BufferSubspanOp : HALInline_PureOp<"buffer.subspan", [
+  DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
+  DeclareOpInterfaceMethods<Util_SizeAwareOp>,
+]> {
+  let summary = [{buffer subspan operation}];
+  let description = [{
+    Returns a reference to a subspan of the buffer.
+  }];
+
+  let arguments = (ins
+    HAL_BufferType:$source_buffer,
+    HAL_DeviceSize:$source_offset,
+    HAL_DeviceSize:$length
+  );
+  let results = (outs
+    HAL_BufferType:$result
+  );
+
+  let assemblyFormat = [{
+    `<` $source_buffer `:` type($source_buffer) `>`
+    `` `[` $source_offset `,` $length `]`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+
+  // TODO(benvanik): folder to elide when offset is 0 and length is all.
+}
+
+def HALInline_BufferLengthOp : HALInline_PureOp<"buffer.length", [
+  DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
+]> {
+  let summary = [{buffer byte length accessor}];
+  let description = [{
+    Returns the allocated size of a buffer in bytes.
+    May be less than the underlying buffer allocation if this is a subspan or
+    view into another buffer.
+  }];
+
+  let arguments = (ins
+    HAL_BufferType:$buffer
+  );
+  let results = (outs
+    HAL_DeviceSize:$result
+  );
+
+  let assemblyFormat = [{
+    `<` $buffer `:` type($buffer) `>`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+
+  let builders = [
+    OpBuilder<(ins "Value":$buffer),
+    [{
+      build($_builder, $_state, $_builder.getIndexType(), buffer);
+    }]>,
+  ];
+
+  let hasFolder = 1;
+}
+
+def HALInline_BufferStorageOp : HALInline_PureOp<"buffer.storage", [
+  DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
+]> {
+  let summary = [{buffer backing storage accessor}];
+  let description = [{
+    Returns the host backing storage of the HAL buffer as a subspan limited to
+    to the buffer's logical range (meaning that byte 0 of the returned buffer is
+    byte 0 of the HAL buffer).
+  }];
+
+  let arguments = (ins
+    HAL_BufferType:$buffer
+  );
+  let results = (outs
+    Util_BufferType:$storage
+  );
+
+  let assemblyFormat = [{
+    `<` $buffer `:` type($buffer) `>`
+    `:` type($storage)
+    attr-dict-with-keyword
+  }];
+
+  let builders = [
+    OpBuilder<(ins "Value":$buffer),
+    [{
+      build($_builder, $_state, $_builder.getType<IREE::Util::BufferType>(), buffer);
+    }]>,
+  ];
+
+  let hasFolder = 1;
+}
+
+//===----------------------------------------------------------------------===//
+// !hal.buffer_view / iree_hal_buffer_view_t
+//===----------------------------------------------------------------------===//
+
+def HALInline_BufferViewCreateOp : HALInline_PureOp<"buffer_view.create", [
+  DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
+]> {
+  let summary = [{buffer view reference initializer}];
+  let description = [{
+    Creates a reference to a buffer with a particular shape and element type.
+    The buffer is not copied and both the original and view references must be
+    synchronized. This makes it easier to associate commonly-carried metadata
+    along with the contents.
+  }];
+
+  let arguments = (ins
+    HAL_BufferType:$buffer,
+    HAL_ElementType:$element_type,
+    HAL_EncodingType:$encoding_type,
+    HAL_Shape:$shape
+  );
+  let results = (outs
+    HAL_BufferView:$result
+  );
+
+  let assemblyFormat = [{
+    `buffer` `(` $buffer `:` type($buffer) `)`
+    `shape` `(` `[` $shape `]` `)`
+    `type` `(` $element_type `)`
+    `encoding` `(` $encoding_type `)`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+
+  let skipDefaultBuilders = 1;
+  let builders = [
+    OpBuilder<(ins
+      "Value":$buffer,
+      "int32_t":$elementType,
+      "int32_t":$encodingType,
+      "ValueRange":$shape
+    )>,
+    OpBuilder<(ins
+      "Value":$buffer,
+      "Value":$elementType,
+      "Value":$encodingType,
+      "ValueRange":$shape
+    )>,
+  ];
+}
+
+def HALInline_BufferViewAssertOp : HALInline_Op<"buffer_view.assert"> {
+  let summary = [{buffer view contents assertion}];
+  let description = [{
+    Asserts that the buffer view contains a data compatible tensor with the
+    given encoding. Program execution will abort as if `std.assert` had been
+    used.
+  }];
+
+  let arguments = (ins
+    HAL_BufferView:$buffer_view,
+    StrAttr:$message,
+    HAL_ElementType:$element_type,
+    HAL_EncodingType:$encoding_type,
+    HAL_Shape:$shape
+  );
+  let results = (outs);
+
+  let assemblyFormat = [{
+    `<` $buffer_view `:` type($buffer_view) `>`
+    `message` `(` $message `)`
+    `shape` `(` `[` $shape `]` `)`
+    `type` `(` $element_type `)`
+    `encoding` `(` $encoding_type `)`
+    attr-dict-with-keyword
+  }];
+
+  // TODO(benvanik): fold away when we know some properties of the buffer view
+  // (such as when we create it ourselves earlier on) or we've already asserted.
+}
+
+def HALInline_BufferViewBufferOp : HALInline_PureOp<"buffer_view.buffer", [
+  DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
+]> {
+  let summary = [{buffer view buffer accessor}];
+  let description = [{
+    Returns the buffer backing this view's contents.
+  }];
+
+  let arguments = (ins
+    HAL_BufferView:$buffer_view
+  );
+  let results = (outs
+    HAL_BufferType:$result
+  );
+
+  let assemblyFormat = [{
+    `<` $buffer_view `:` type($buffer_view) `>`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+
+  let hasCanonicalizer = 1;
+}
+
+def HALInline_BufferViewElementTypeOp : HALInline_PureOp<"buffer_view.element_type"> {
+  let summary = [{buffer view element type query}];
+  let description = [{
+    Returns the element type of the buffer view.
+  }];
+
+  let arguments = (ins
+    HAL_BufferView:$buffer_view
+  );
+  let results = (outs
+    HAL_ElementType:$result
+  );
+
+  let assemblyFormat = [{
+    `<` $buffer_view `:` type($buffer_view) `>`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+}
+
+def HALInline_BufferViewEncodingTypeOp : HALInline_PureOp<"buffer_view.encoding_type"> {
+  let summary = [{buffer view encoding type query}];
+  let description = [{
+    Returns the encoding type of the buffer view.
+  }];
+
+  let arguments = (ins
+    HAL_BufferView:$buffer_view
+  );
+  let results = (outs
+    HAL_EncodingType:$result
+  );
+
+  let assemblyFormat = [{
+    `<` $buffer_view `:` type($buffer_view) `>`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+}
+
+def HALInline_BufferViewRankOp : HALInline_PureOp<"buffer_view.rank"> {
+  let summary = [{buffer view rank query}];
+  let description = [{
+    Returns the rank of the buffer view.
+  }];
+
+  let arguments = (ins
+    HAL_BufferView:$buffer_view
+  );
+  let results = (outs
+    HAL_Dim:$result
+  );
+
+  let assemblyFormat = [{
+    `<` $buffer_view `:` type($buffer_view) `>`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+}
+
+def HALInline_BufferViewDimOp : HALInline_PureOp<"buffer_view.dim"> {
+  let summary = [{buffer view dimension value query}];
+  let description = [{
+    Returns the value of the given dimension.
+  }];
+
+  let arguments = (ins
+    HAL_BufferView:$buffer_view,
+    IndexAttr:$index
+  );
+  let results = (outs
+    HAL_Dim:$result
+  );
+
+  let assemblyFormat = [{
+    `<` $buffer_view `:` type($buffer_view) `>`
+    `` `[` $index `]`
+    `:` type($result)
+    attr-dict-with-keyword
+  }];
+}
+
+def HALInline_BufferViewTraceOp : HALInline_Op<"buffer_view.trace", []> {
+  let summary = [{trace value(s) operation}];
+  let description = [{
+    Traces out to a runtime trace sink (console, log file, etc) the given buffer
+    views and titles them with the given key. The key is informational only and
+    useful for titling/marking specific sets of buffers for easier searching.
+  }];
+
+  let arguments = (ins
+    StrAttr:$key,
+    Variadic<HAL_BufferView>:$operands
+  );
+
+  let assemblyFormat = [{
+    $operands `:` type($operands)
+    attr-dict-with-keyword
+  }];
+}
+
+//===----------------------------------------------------------------------===//
+// !hal.device / iree_hal_device_t
+//===----------------------------------------------------------------------===//
+
+def HALInline_DeviceQueryOp :
+    HALInline_PureOp<"device.query"> {
+  let summary = [{returns a runtime configuration parameter from the device}];
+  let description = [{
+    Queries a device configuration parameter with the given key.
+    Returns a status indicating whether the pair was recognized/available and if
+    it was the value converted to the specified type. Queries must return the
+    same value for the lifetime of the module though may vary from run to run.
+
+    This is roughly equivalent to the `sysconf` linux syscall
+    (https://man7.org/linux/man-pages/man3/sysconf.3.html) in that the exact
+    set of keys available and their interpretation is target-dependent. If there
+    is a HAL match attribute (`#hal.device.match.*`) or op
+    (`hal.device.match.*`) prefer to use that in order to get compile-time
+    propagation when the target is specified and elide the runtime query and
+    get compile-time verification when a runtime query is required.
+
+    Users of the op must check the `ok` result before using the value as what
+    set of keys is available may change over time. If in doubt: don't use this.
+    Each key used adds additional versioning and testing complexity as runtime
+    code path changes will explode combinatorially and should be treated with as
+    much care as a binary file format change. Keys should be prefixed with `ex.`
+    when experimental indicating that they are not expected to be present
+    forever; all non-experimental keys should be vetted.
+
+    Well-known keys: (none yet)
+  }];
+
+  let arguments = (ins
+    StrAttr:$category,
+    StrAttr:$key,
+    OptionalAttr<AnyAttr>:$default_value
+  );
+  let results = (outs
+    I1:$ok,
+    AnyType:$value
+  );
+
+  let assemblyFormat = [{
+    `key` `(` $category `:` `` `:` $key `)`
+    `:` type($ok) `,` type($value)
+    (`=` $default_value^)?
+    attr-dict-with-keyword
+  }];
+
+  let hasVerifier = 1;
+}
+
+#endif  // IREE_DIALECT_MODULES_HAL_INLINE_OPS
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/BUILD
new file mode 100644
index 0000000..98a6f8f
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/BUILD
@@ -0,0 +1,28 @@
+# 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
+
+load("//build_tools/bazel:iree_lit_test.bzl", "iree_lit_test_suite")
+load("//build_tools/bazel:enforce_glob.bzl", "enforce_glob")
+
+package(
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_lit_test_suite(
+    name = "lit",
+    srcs = enforce_glob(
+        [
+            "buffer_folding.mlir",
+        ],
+        include = ["*.mlir"],
+    ),
+    cfg = "//compiler:lit.cfg.py",
+    tools = [
+        "//tools:iree-opt",
+        "@llvm-project//llvm:FileCheck",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/CMakeLists.txt
new file mode 100644
index 0000000..e265ec7
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/CMakeLists.txt
@@ -0,0 +1,23 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/BUILD          #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_lit_test_suite(
+  NAME
+    lit
+  SRCS
+    "buffer_folding.mlir"
+  TOOLS
+    FileCheck
+    iree-opt
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/buffer_folding.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/buffer_folding.mlir
new file mode 100644
index 0000000..eead77f
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR/test/buffer_folding.mlir
@@ -0,0 +1,43 @@
+// RUN: iree-opt --split-input-file --canonicalize -cse %s | iree-opt --allow-unregistered-dialect --split-input-file | FileCheck %s
+
+// CHECK-LABEL: func @fold_buffer_length
+// CHECK-SAME: (%[[LENGTH:.+]]: index)
+func.func @fold_buffer_length(%length: index) -> index {
+  %c64 = arith.constant 64 : index
+  %buffer, %storage = hal_inline.buffer.allocate alignment(%c64) : !hal.buffer{%length} in !util.buffer
+  // CHECK-NOT: hal_inline.buffer.length
+  %queried_length = hal_inline.buffer.length<%buffer : !hal.buffer> : index
+  // CHECK: return %[[LENGTH]]
+  return %queried_length : index
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_buffer_storage
+func.func @fold_buffer_storage(%length: index) -> !util.buffer {
+  %c64 = arith.constant 64 : index
+  // CHECK: %[[BUFFER:.+]], %[[STORAGE:.+]] = hal_inline.buffer.allocate
+  %buffer, %storage = hal_inline.buffer.allocate alignment(%c64) : !hal.buffer{%length} in !util.buffer
+  // CHECK-NOT: hal_inline.buffer.storage
+  %queried_storage = hal_inline.buffer.storage<%buffer : !hal.buffer> : !util.buffer
+  // CHECK: return %[[STORAGE]]
+  return %queried_storage : !util.buffer
+}
+
+// -----
+
+// CHECK-LABEL: func @skip_buffer_view_buffer
+// CHECK-SAME: (%[[BUFFER:.+]]: !hal.buffer)
+func.func @skip_buffer_view_buffer(%buffer: !hal.buffer) -> !hal.buffer {
+  %c1 = arith.constant 1 : i32
+  %c10 = arith.constant 10 : index
+  %c11 = arith.constant 11 : index
+  %c32 = arith.constant 32 : i32
+  %view = hal_inline.buffer_view.create buffer(%buffer : !hal.buffer)
+                                        shape([%c10, %c11])
+                                        type(%c32)
+                                        encoding(%c1) : !hal.buffer_view
+  %view_buffer = hal_inline.buffer_view.buffer<%view : !hal.buffer_view> : !hal.buffer
+  // CHECK: return %[[BUFFER]]
+  return %view_buffer : !hal.buffer
+}
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/BUILD
new file mode 100644
index 0000000..6551edb
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/BUILD
@@ -0,0 +1,83 @@
+# 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
+
+load("//build_tools/bazel:build_defs.oss.bzl", "iree_compiler_cc_library")
+load("//build_tools/bazel:iree_tablegen.bzl", "iree_gentbl_cc_library")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_compiler_cc_library(
+    name = "Transforms",
+    srcs = [
+        "Conversion.cpp",
+        "InlineExecutables.cpp",
+        "Passes.cpp",
+    ],
+    hdrs = ["Passes.h"],
+    deps = [
+        ":PassHeaders",
+        "//compiler/src/iree/compiler/Dialect/HAL/Conversion/StandardToHAL",
+        "//compiler/src/iree/compiler/Dialect/HAL/Conversion/UtilToHAL",
+        "//compiler/src/iree/compiler/Dialect/HAL/IR:HALDialect",
+        "//compiler/src/iree/compiler/Dialect/HAL/Target",
+        "//compiler/src/iree/compiler/Dialect/HAL/Transforms",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR:HALInlineDialect",
+        "//compiler/src/iree/compiler/Dialect/Stream/IR",
+        "//compiler/src/iree/compiler/Dialect/Util/Conversion",
+        "//compiler/src/iree/compiler/Dialect/Util/IR",
+        "//compiler/src/iree/compiler/Dialect/Util/Transforms",
+        "//compiler/src/iree/compiler/Utils",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//mlir:AffineDialect",
+        "@llvm-project//mlir:ArithmeticDialect",
+        "@llvm-project//mlir:ArithmeticTransforms",
+        "@llvm-project//mlir:FuncDialect",
+        "@llvm-project//mlir:FuncTransforms",
+        "@llvm-project//mlir:IR",
+        "@llvm-project//mlir:MathDialect",
+        "@llvm-project//mlir:MathTransforms",
+        "@llvm-project//mlir:MemRefDialect",
+        "@llvm-project//mlir:Pass",
+        "@llvm-project//mlir:SCFDialect",
+        "@llvm-project//mlir:SCFToControlFlow",
+        "@llvm-project//mlir:Support",
+        "@llvm-project//mlir:Transforms",
+    ],
+)
+
+iree_compiler_cc_library(
+    name = "PassHeaders",
+    hdrs = [
+        "PassDetail.h",
+        "Passes.h",
+        "Passes.h.inc",
+    ],
+    deps = [
+        ":PassesIncGen",
+        "@llvm-project//mlir:Pass",
+        "@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/Modules/HAL/Inline/Transforms/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/CMakeLists.txt
new file mode 100644
index 0000000..c33d9fe
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/CMakeLists.txt
@@ -0,0 +1,79 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/BUILD       #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_cc_library(
+  NAME
+    Transforms
+  HDRS
+    "Passes.h"
+  SRCS
+    "Conversion.cpp"
+    "InlineExecutables.cpp"
+    "Passes.cpp"
+  DEPS
+    ::PassHeaders
+    LLVMSupport
+    MLIRAffineDialect
+    MLIRArithmeticDialect
+    MLIRArithmeticTransforms
+    MLIRFuncDialect
+    MLIRFuncTransforms
+    MLIRIR
+    MLIRMathDialect
+    MLIRMathTransforms
+    MLIRMemRefDialect
+    MLIRPass
+    MLIRSCFDialect
+    MLIRSCFToControlFlow
+    MLIRSupport
+    MLIRTransforms
+    iree::compiler::Dialect::HAL::Conversion::StandardToHAL
+    iree::compiler::Dialect::HAL::Conversion::UtilToHAL
+    iree::compiler::Dialect::HAL::IR::HALDialect
+    iree::compiler::Dialect::HAL::Target
+    iree::compiler::Dialect::HAL::Transforms
+    iree::compiler::Dialect::Modules::HAL::Inline::Conversion::HALToHALInline
+    iree::compiler::Dialect::Modules::HAL::Inline::Conversion::StreamToHALInline
+    iree::compiler::Dialect::Modules::HAL::Inline::IR
+    iree::compiler::Dialect::Modules::HAL::Inline::IR::HALInlineDialect
+    iree::compiler::Dialect::Stream::IR
+    iree::compiler::Dialect::Util::Conversion
+    iree::compiler::Dialect::Util::IR
+    iree::compiler::Dialect::Util::Transforms
+    iree::compiler::Utils
+  PUBLIC
+)
+
+iree_cc_library(
+  NAME
+    PassHeaders
+  HDRS
+    "PassDetail.h"
+    "Passes.h"
+    "Passes.h.inc"
+  DEPS
+    ::PassesIncGen
+    MLIRPass
+    MLIRTransforms
+  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/Modules/HAL/Inline/Transforms/Conversion.cpp b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Conversion.cpp
new file mode 100644
index 0000000..a0613f9
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Conversion.cpp
@@ -0,0 +1,103 @@
+// 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/Dialect/HAL/Conversion/StandardToHAL/ConvertStandardToHAL.h"
+#include "iree/compiler/Dialect/HAL/Conversion/UtilToHAL/ConvertUtilToHAL.h"
+#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Conversion/HALToHALInline/ConvertHALToHALInline.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Conversion/StreamToHALInline/ConvertStreamToHALInline.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Transforms/PassDetail.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h"
+#include "iree/compiler/Dialect/Stream/IR/StreamDialect.h"
+#include "iree/compiler/Dialect/Util/Conversion/ConversionPatterns.h"
+#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
+#include "llvm/ADT/STLExtras.h"
+#include "mlir/Dialect/Affine/IR/AffineOps.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/Math/IR/Math.h"
+#include "mlir/Dialect/SCF/IR/SCF.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/PatternMatch.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Pass/PassRegistry.h"
+#include "mlir/Transforms/DialectConversion.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+namespace Inline {
+
+// Runs conversion with registered input dialects.
+class ConversionPass : public ConversionBase<ConversionPass> {
+ public:
+  void getDependentDialects(DialectRegistry &registry) const override {
+    registry.insert<IREE::Util::UtilDialect, IREE::HAL::HALDialect,
+                    IREE::HAL::Inline::HALInlineDialect,
+                    mlir::arith::ArithmeticDialect>();
+  }
+
+  void runOnOperation() override {
+    auto *context = &getContext();
+
+    // Ensure all input dialects go away.
+    ConversionTarget conversionTarget(*context);
+    conversionTarget
+        .addLegalDialect<mlir::func::FuncDialect, mlir::scf::SCFDialect,
+                         mlir::arith::ArithmeticDialect>();
+
+    TypeConverter typeConverter;
+    RewritePatternSet patterns(context);
+
+    // Pass-through.
+    typeConverter.addConversion([](IndexType type) { return type; });
+    typeConverter.addConversion([](IntegerType type) { return type; });
+    typeConverter.addConversion([](FloatType type) { return type; });
+    typeConverter.addConversion(
+        [](IREE::Util::BufferType type) { return type; });
+
+    // Convert stream into `hal_inline`, which mostly entails ignoring ops.
+    conversionTarget.addLegalDialect<IREE::HAL::Inline::HALInlineDialect>();
+    populateStreamToHALInlinePatterns(context, conversionTarget, typeConverter,
+                                      patterns);
+
+    // Convert some common things into HAL, reusing those conversions.
+    populateStandardToHALPatterns(context, conversionTarget, typeConverter,
+                                  patterns);
+    populateUtilToHALPatterns(context, conversionTarget, typeConverter,
+                              patterns);
+
+    // Convert any full `hal` ops into `hal_inline` ops.
+    conversionTarget.addIllegalDialect<IREE::HAL::HALDialect>();
+    populateHALToHALInlinePatterns(context, conversionTarget, typeConverter,
+                                   patterns);
+
+    // Generic conversion.
+    conversionTarget.addLegalDialect<IREE::Util::UtilDialect>();
+    populateUtilConversionPatterns(context, conversionTarget, typeConverter,
+                                   patterns);
+    populateGenericStructuralConversionPatterns(context, conversionTarget,
+                                                typeConverter, patterns);
+
+    if (failed(applyPartialConversion(getOperation(), conversionTarget,
+                                      std::move(patterns)))) {
+      getOperation().emitError()
+          << "conversion to the hal_inline dialect failed";
+      return signalPassFailure();
+    }
+  }
+};
+
+std::unique_ptr<OperationPass<mlir::ModuleOp>> createConversionPass() {
+  return std::make_unique<ConversionPass>();
+}
+
+}  // namespace Inline
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/InlineExecutables.cpp b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/InlineExecutables.cpp
new file mode 100644
index 0000000..cfa6af2
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/InlineExecutables.cpp
@@ -0,0 +1,418 @@
+// 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/Dialect/HAL/IR/HALDialect.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Transforms/PassDetail.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h"
+#include "iree/compiler/Dialect/Stream/IR/StreamDialect.h"
+#include "iree/compiler/Dialect/Stream/IR/StreamOps.h"
+#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
+#include "iree/compiler/Utils/IndexSet.h"
+#include "iree/compiler/Utils/ModuleUtils.h"
+#include "llvm/ADT/STLExtras.h"
+#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/SCF/IR/SCF.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Pass/PassRegistry.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+namespace Inline {
+
+class InlineExecutablesPass
+    : public InlineExecutablesBase<InlineExecutablesPass> {
+ public:
+  void getDependentDialects(DialectRegistry &registry) const override {
+    registry
+        .insert<IREE::Util::UtilDialect, IREE::HAL::HALDialect,
+                IREE::HAL::Inline::HALInlineDialect, arith::ArithmeticDialect,
+                func::FuncDialect, scf::SCFDialect>();
+  }
+
+  void runOnOperation() override {
+    auto moduleOp = getOperation();
+
+    // Inline variants and produce a function map.
+    DenseMap<Attribute, Attribute> exportToFuncMap;
+    SymbolTableCollection symbolTables;
+    for (auto executableOp : llvm::make_early_inc_range(
+             moduleOp.getOps<IREE::HAL::ExecutableOp>())) {
+      // Inline each variant.
+      for (auto variantOp :
+           executableOp.getOps<IREE::HAL::ExecutableVariantOp>()) {
+        if (failed(inlineVariant(executableOp, variantOp, moduleOp,
+                                 exportToFuncMap, symbolTables))) {
+          return signalPassFailure();
+        }
+      }
+
+      // Drop executable after information has been extracted and the workgroup
+      // code has been inlined.
+      executableOp.erase();
+    }
+
+    // Annotate all dispatches with the target function.
+    for (auto funcOp : moduleOp.getOps<mlir::FunctionOpInterface>()) {
+      funcOp.walk([&](IREE::Stream::CmdDispatchOp dispatchOp) {
+        // Specify new target function that conversion can use to make the call.
+        auto targetFuncName =
+            exportToFuncMap[dispatchOp.getEntryPoint()].cast<StringAttr>();
+        assert(targetFuncName && "missing mapping");
+        dispatchOp->setAttr("hal_inline.target",
+                            FlatSymbolRefAttr::get(targetFuncName));
+      });
+    }
+  }
+
+  LogicalResult inlineVariant(IREE::HAL::ExecutableOp executableOp,
+                              IREE::HAL::ExecutableVariantOp variantOp,
+                              mlir::ModuleOp targetModuleOp,
+                              DenseMap<Attribute, Attribute> &exportToFuncMap,
+                              SymbolTableCollection &symbolTables) {
+    auto innerModuleOp = variantOp.getInnerModule();
+    auto innerSymbolTable = symbolTables.getSymbolTable(innerModuleOp);
+    auto innerModuleBuilder = OpBuilder::atBlockEnd(innerModuleOp.getBody());
+
+    // We want to merge the module ahead of the exported functions to ensure
+    // initializer order is preserved.
+    OpBuilder targetModuleBuilder(executableOp);
+
+    // Build each dispatch function wrapper.
+    auto indexType = innerModuleBuilder.getIndexType();
+    auto i32Type = innerModuleBuilder.getI32Type();
+    auto bufferType = innerModuleBuilder.getType<IREE::Util::BufferType>();
+    for (auto exportOp : variantOp.getOps<IREE::HAL::ExecutableExportOp>()) {
+      // Build dispatch function signature that the stream.cmd.dispatch ops will
+      // map to.
+      auto layoutAttr = exportOp.getLayout();
+      size_t totalBindingCount = 0;
+      for (auto setLayout : layoutAttr.getSetLayouts()) {
+        totalBindingCount += setLayout.getBindings().size();
+      }
+      SmallVector<Type> inputTypes;
+      inputTypes.append(exportOp.getWorkgroupCountBody()->getNumArguments() - 1,
+                        indexType);  // workload
+      inputTypes.append(layoutAttr.getPushConstants(), i32Type);
+      inputTypes.append(totalBindingCount, bufferType);  // buffers
+      inputTypes.append(totalBindingCount, indexType);   // offsets
+      inputTypes.append(totalBindingCount, indexType);   // lengths
+      auto dispatchFuncType =
+          innerModuleBuilder.getFunctionType(inputTypes, {});
+
+      // Create the function and insert into the module.
+      auto dispatchFuncOp = func::FuncOp::create(
+          exportOp.getLoc(),
+          ("__dispatch_" + executableOp.getName() + "_" + exportOp.getName())
+              .str(),
+          dispatchFuncType);
+      dispatchFuncOp.setPrivate();
+      innerSymbolTable.insert(dispatchFuncOp,
+                              innerModuleBuilder.getInsertionPoint());
+      innerModuleBuilder.setInsertionPointAfter(dispatchFuncOp);
+
+      // Build the dispatch function by calling the target function in a loop.
+      auto bodyFuncOp =
+          innerSymbolTable.lookup<func::FuncOp>(exportOp.getName());
+      if (bodyFuncOp.isPublic()) {
+        if (failed(rewriteWorkgroupSignature(layoutAttr, totalBindingCount,
+                                             bodyFuncOp))) {
+          return failure();
+        }
+        bodyFuncOp.setPrivate();  // so we only do it once
+      }
+      buildDispatchFunc(exportOp, layoutAttr, totalBindingCount, bodyFuncOp,
+                        dispatchFuncOp);
+
+      // Map from what the stream.cmd.dispatch ops is using to the new function.
+      auto exportTargetAttr =
+          SymbolRefAttr::get(executableOp.getNameAttr(),
+                             {SymbolRefAttr::get(exportOp.getNameAttr())});
+      exportToFuncMap[exportTargetAttr] = dispatchFuncOp.getNameAttr();
+    }
+
+    // Merge the source executable module into the target host module.
+    if (failed(mergeModuleInto(innerModuleOp, targetModuleOp,
+                               targetModuleBuilder))) {
+      return failure();
+    }
+
+    return success();
+  }
+
+  // Rewrites a workgroup body function signature to a flattened list.
+  //
+  // Body (as translated):
+  //   (local_memory, [constants], [bindings],
+  //    workgroup_x, workgroup_y, workgroup_z,
+  //    workgroup_size_x, workgroup_size_y, workgroup_size_z,
+  //    workgroup_count_x, workgroup_count_y, workgroup_count_z)
+  //
+  // Body after rewrite:
+  //   (local_memory, constants..., bindings...,
+  //    workgroup_x, workgroup_y, workgroup_z,
+  //    workgroup_size_x, workgroup_size_y, workgroup_size_z,
+  //    workgroup_count_x, workgroup_count_y, workgroup_count_z)
+  //
+  // To make this process easier and lighten the load on the downstream passes
+  // we muck with the ABI to pass a flattened list of constants and bindings.
+  // Whenever better IPO and util.list optimizations are added we could back
+  // this out to keep things vanilla and have fewer places making assumptions
+  // about the function signatures.
+  LogicalResult rewriteWorkgroupSignature(
+      IREE::HAL::ExecutableLayoutAttr layoutAttr, size_t totalBindingCount,
+      func::FuncOp bodyFuncOp) {
+    auto *entryBlock = &bodyFuncOp.front();
+    auto builder = OpBuilder::atBlockBegin(entryBlock);
+    auto indexType = builder.getIndexType();
+    auto i32Type = builder.getI32Type();
+    auto bufferType = builder.getType<IREE::Util::BufferType>();
+
+    // There may be nicer ways of doing this but I can't find them.
+    // We build a new list of argument types and insert them as we go. This lets
+    // us map the arguments over and replace usage such that by the end we can
+    // slice off the original arguments as they'll have no more uses.
+    unsigned originalArgCount = entryBlock->getNumArguments();
+    SmallVector<Type> newArgTypes;
+    unsigned argOffset = 0;
+
+    // Local memory is carried across as-is.
+    auto localMemoryArg = entryBlock->getArgument(argOffset++);
+    newArgTypes.push_back(bufferType);
+    localMemoryArg.replaceAllUsesWith(
+        entryBlock->addArgument(bufferType, localMemoryArg.getLoc()));
+
+    // Expand push constants by replacing buffer accesses with the flattened
+    // args.
+    newArgTypes.append(layoutAttr.getPushConstants(), i32Type);
+    auto constantBuffer = entryBlock->getArgument(argOffset++);
+    SmallVector<Value> constantArgs;
+    for (unsigned i = 0; i < layoutAttr.getPushConstants(); ++i) {
+      constantArgs.push_back(
+          entryBlock->addArgument(i32Type, constantBuffer.getLoc()));
+    }
+    if (failed(replaceBufferAccesses(constantBuffer, constantArgs))) {
+      return failure();
+    }
+
+    // Expand buffer list by replacing list accesses with the flattened args.
+    newArgTypes.append(totalBindingCount, bufferType);
+    auto bindingList = entryBlock->getArgument(argOffset++);
+    SmallVector<Value> bindingArgs;
+    for (unsigned i = 0; i < totalBindingCount; ++i) {
+      bindingArgs.push_back(
+          entryBlock->addArgument(bufferType, bindingList.getLoc()));
+    }
+    if (failed(replaceListAccesses(bindingList, bindingArgs))) {
+      return failure();
+    }
+
+    // Take care of the workgroup id/size/count tuples.
+    for (unsigned i = 0; i < 3 * /*xyz=*/3; ++i) {
+      newArgTypes.push_back(indexType);
+      auto oldArg = entryBlock->getArgument(argOffset++);
+      oldArg.replaceAllUsesWith(
+          entryBlock->addArgument(indexType, oldArg.getLoc()));
+    }
+
+    // Erase the original args.
+    for (unsigned i = 0; i < originalArgCount; ++i) {
+      entryBlock->eraseArgument(0);
+    }
+
+    // Update function signature to reflect the entry block args.
+    bodyFuncOp.setType(
+        builder.getFunctionType(newArgTypes, bodyFuncOp.getResultTypes()));
+
+    return success();
+  }
+
+  // Replaces trivial constant index accesses to a buffer with their values.
+  // This is an extremely poor optimization that we should remove if buffer
+  // ever gets store-load forwarding - we could just create the buffer, store
+  // the elements, and let that take care of the rest. Today it doesn't do that.
+  LogicalResult replaceBufferAccesses(Value buffer, ValueRange elements) {
+    for (auto user : llvm::make_early_inc_range(buffer.getUsers())) {
+      if (auto sizeOp = dyn_cast<IREE::Util::BufferSizeOp>(user)) {
+        // Ignored but we need to get rid of it.
+        // TODO(benvanik): see if we can allow this through; today it will pin
+        // the function argument (constants most likely) and cause us to fail to
+        // remove it later on.
+        Value dummySize = OpBuilder(sizeOp).create<arith::ConstantIndexOp>(
+            sizeOp.getLoc(), 0xCAFEF00D);
+        sizeOp.replaceAllUsesWith(dummySize);
+        sizeOp.erase();
+        continue;
+      } else if (auto loadOp = dyn_cast<IREE::Util::BufferLoadOp>(user)) {
+        APInt index;
+        if (matchPattern(loadOp.getSourceOffset(), m_ConstantInt(&index))) {
+          loadOp.replaceAllUsesWith(
+              elements[index.getSExtValue() / sizeof(uint32_t)]);
+          loadOp.erase();
+          continue;
+        } else {
+          return loadOp.emitOpError(
+              "unhandled dynamic buffer access; must be static");
+        }
+      } else if (auto loadOp = dyn_cast<memref::LoadOp>(user)) {
+        if (loadOp.indices().size() != 1) {
+          return loadOp.emitOpError(
+              "expected memrefs to have been flattened before inlining "
+              "executables");
+        }
+        APInt index;
+        if (matchPattern(loadOp.indices()[0], m_ConstantInt(&index))) {
+          loadOp.replaceAllUsesWith(elements[index.getSExtValue()]);
+          loadOp.erase();
+          continue;
+        } else {
+          return loadOp.emitOpError(
+              "unhandled dynamic buffer access; must be static");
+        }
+      } else {
+        return user->emitOpError(
+            "unhandled buffer access op; only loads are supported");
+      }
+    }
+    return success();
+  }
+
+  // Replaces trivial constant index accesses to a list with their values.
+  // util.list store-load forwarding could do this instead.
+  LogicalResult replaceListAccesses(Value list, ValueRange elements) {
+    for (auto user : llvm::make_early_inc_range(list.getUsers())) {
+      if (auto getOp = dyn_cast<IREE::Util::ListGetOp>(user)) {
+        APInt index;
+        if (matchPattern(getOp.getIndex(), m_ConstantInt(&index))) {
+          getOp.replaceAllUsesWith(elements[index.getSExtValue()]);
+          getOp.erase();
+          continue;
+        } else {
+          return getOp.emitOpError(
+              "unhandled dynamic list access; must be static");
+        }
+      } else {
+        return user->emitOpError(
+            "unhandled list access op; only gets are supported");
+      }
+    }
+    return success();
+  }
+
+  // Builds a function that calls a workgroup body and marshals arguments.
+  //
+  // Incoming:
+  //   (workload..., push_constants...,
+  //    binding_buffers..., binding_offsets..., binding_lengths...)
+  // Body (as translated):
+  //   (local_memory, [constants], [bindings],
+  //    workgroup_x, workgroup_y, workgroup_z,
+  //    workgroup_size_x, workgroup_size_y, workgroup_size_z,
+  //    workgroup_count_x, workgroup_count_y, workgroup_count_z)
+  void buildDispatchFunc(IREE::HAL::ExecutableExportOp exportOp,
+                         IREE::HAL::ExecutableLayoutAttr layoutAttr,
+                         size_t totalBindingCount, func::FuncOp bodyFuncOp,
+                         func::FuncOp dispatchFuncOp) {
+    auto loc = exportOp.getLoc();
+    auto builder = OpBuilder::atBlockBegin(dispatchFuncOp.addEntryBlock());
+    IndexSet indexSet(loc, builder);
+    auto bufferType = builder.getType<IREE::Util::BufferType>();
+
+    SmallVector<Value> workgroupArgs;
+
+    // Calculate the XYZ workgroup count from the export function.
+    // There may be multiple exports pointing at the same body with different
+    // workgroup count functions.
+    unsigned workloadArgCount =
+        exportOp.getWorkgroupCountBody()->getNumArguments() - 1;
+    unsigned argOffset = 0;
+    SmallVector<Value> workload;
+    workload.reserve(workloadArgCount);
+    for (unsigned i = 0; i < workloadArgCount; ++i) {
+      workload.push_back(dispatchFuncOp.getArgument(argOffset++));
+    }
+    Value device = builder.create<IREE::Util::NullOp>(
+        loc, builder.getType<IREE::HAL::DeviceType>());
+    auto workgroupCount =
+        exportOp.calculateWorkgroupCount(loc, device, workload, builder);
+
+    // For now we don't handle local memory.
+    Value localMemory = builder.create<IREE::Util::NullOp>(loc, bufferType);
+    workgroupArgs.push_back(localMemory);
+
+    // Pass all constants through.
+    for (int64_t i = 0; i < layoutAttr.getPushConstants(); ++i) {
+      workgroupArgs.push_back(dispatchFuncOp.getArgument(argOffset++));
+    }
+
+    // Pass all buffers through as subspans with the binding offset and length
+    // factored in. IPO can propagate the subspans (hopefully).
+    for (size_t i = 0; i < totalBindingCount; ++i) {
+      auto bindingBuffer = dispatchFuncOp.getArgument(argOffset + i);
+      auto bindingOffset =
+          dispatchFuncOp.getArgument(argOffset + totalBindingCount + i);
+      auto bindingLength = dispatchFuncOp.getArgument(
+          argOffset + totalBindingCount + totalBindingCount + i);
+      Value bufferSize =
+          builder.create<IREE::Util::BufferSizeOp>(loc, bindingBuffer);
+      Value bindingView = builder.create<IREE::Util::BufferSubspanOp>(
+          loc, bindingBuffer, bufferSize, bindingOffset, bindingLength);
+      workgroupArgs.push_back(bindingView);
+    }
+
+    int workgroupXYZOffset = workgroupArgs.size();
+    workgroupArgs.push_back(nullptr);            // workgroup_x, set below
+    workgroupArgs.push_back(nullptr);            // workgroup_y, set below
+    workgroupArgs.push_back(nullptr);            // workgroup_z, set below
+    workgroupArgs.append(3, indexSet.get(1));    // workgroup_size_xyz
+    workgroupArgs.push_back(workgroupCount[0]);  // workgroup_count_x
+    workgroupArgs.push_back(workgroupCount[1]);  // workgroup_count_y
+    workgroupArgs.push_back(workgroupCount[2]);  // workgroup_count_z
+
+    // Z -> Y -> Z loop nest.
+    builder.create<scf::ForOp>(
+        loc, indexSet.get(0), workgroupCount[2], indexSet.get(1), ValueRange{},
+        [&](OpBuilder &forZBuilder, Location loc, Value iz, ValueRange iters) {
+          workgroupArgs[workgroupXYZOffset + 2] = iz;
+          forZBuilder.create<scf::ForOp>(
+              loc, indexSet.get(0), workgroupCount[1], indexSet.get(1),
+              ValueRange{},
+              [&](OpBuilder &forYBuilder, Location loc, Value iy,
+                  ValueRange iters) {
+                workgroupArgs[workgroupXYZOffset + 1] = iy;
+                forYBuilder.create<scf::ForOp>(
+                    loc, indexSet.get(0), workgroupCount[0], indexSet.get(1),
+                    ValueRange{},
+                    [&](OpBuilder &forXBuilder, Location loc, Value ix,
+                        ValueRange iters) {
+                      workgroupArgs[workgroupXYZOffset + 0] = ix;
+                      forXBuilder.create<func::CallOp>(loc, bodyFuncOp,
+                                                       workgroupArgs);
+                      forXBuilder.create<scf::YieldOp>(loc);
+                    });
+                forYBuilder.create<scf::YieldOp>(loc);
+              });
+          forZBuilder.create<scf::YieldOp>(loc);
+        });
+
+    builder.create<func::ReturnOp>(loc);
+  }
+};
+
+std::unique_ptr<OperationPass<mlir::ModuleOp>> createInlineExecutablesPass() {
+  return std::make_unique<InlineExecutablesPass>();
+}
+
+}  // namespace Inline
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/PassDetail.h b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/PassDetail.h
new file mode 100644
index 0000000..55e647e
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/PassDetail.h
@@ -0,0 +1,29 @@
+// 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_DIALECT_MODULES_HAL_INLINE_TRANSFORMS_PASS_DETAIL_H_
+#define IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_TRANSFORMS_PASS_DETAIL_H_
+
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Pass/Pass.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+namespace Inline {
+
+#define GEN_PASS_CLASSES
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h.inc"
+
+}  // namespace Inline
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_TRANSFORMS_PASS_DETAIL_H_
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.cpp b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.cpp
new file mode 100644
index 0000000..ddd9f4c
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.cpp
@@ -0,0 +1,115 @@
+// 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/Dialect/Modules/HAL/Inline/Transforms/Passes.h"
+
+#include <memory>
+
+#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
+#include "iree/compiler/Dialect/Util/Transforms/Passes.h"
+#include "iree/compiler/Utils/PassUtils.h"
+#include "mlir/Dialect/Arithmetic/Transforms/Passes.h"
+#include "mlir/Dialect/Func/Transforms/Passes.h"
+#include "mlir/Pass/PassRegistry.h"
+#include "mlir/Transforms/Passes.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+namespace Inline {
+
+using FunctionLikeNest = MultiOpNest<func::FuncOp, IREE::Util::InitializerOp>;
+
+//===----------------------------------------------------------------------===//
+// Utilities
+//===----------------------------------------------------------------------===//
+
+static void addCleanupPatterns(OpPassManager &passManager) {
+  // Standard MLIR cleanup.
+  passManager.addPass(mlir::createCanonicalizerPass());
+  passManager.addPass(mlir::createCSEPass());
+
+  FunctionLikeNest(passManager)
+      // Simplify util.global accesses; this can help with data flow tracking as
+      // redundant store-loads are removed.
+      .addPass(IREE::Util::createSimplifyGlobalAccessesPass);
+
+  // Cleanup and canonicalization of util.global (and other util ops).
+  passManager.addPass(IREE::Util::createApplyPatternsPass());
+  passManager.addPass(IREE::Util::createFoldGlobalsPass());
+  passManager.addPass(IREE::Util::createFuseGlobalsPass());
+}
+
+//===----------------------------------------------------------------------===//
+// -iree-hal-inline-static-transformation-pipeline
+//===----------------------------------------------------------------------===//
+
+void buildHALInlineStaticTransformPassPipeline(
+    OpPassManager &passManager, const TargetOptions &targetOptions) {
+  //----------------------------------------------------------------------------
+  // Device assignment and interface materialization
+  //----------------------------------------------------------------------------
+
+  IREE::HAL::buildHALConfigurationPassPipeline(passManager, targetOptions);
+
+  //----------------------------------------------------------------------------
+  // Executable translation
+  //----------------------------------------------------------------------------
+
+  // Translate each executable down to common MLIR dialects.
+  passManager.addNestedPass<IREE::HAL::ExecutableOp>(
+      IREE::HAL::createTranslateExecutablesPass());
+
+  // Inline the translated executable functions.
+  // We preserve the executables for their metadata used during conversion.
+  passManager.addPass(IREE::HAL::Inline::createInlineExecutablesPass());
+  addCleanupPatterns(passManager);
+
+  //----------------------------------------------------------------------------
+  // Conversion
+  //----------------------------------------------------------------------------
+
+  // Convert from stream to hal_inline.
+  passManager.addPass(IREE::HAL::Inline::createConversionPass());
+
+  // Propagate buffer subranges across the program.
+  passManager.addPass(IREE::Util::createPropagateSubrangesPass());
+
+  //----------------------------------------------------------------------------
+  // Cleanup and canonicalization
+  //----------------------------------------------------------------------------
+
+  addCleanupPatterns(passManager);
+}
+
+//===----------------------------------------------------------------------===//
+// Registration
+//===----------------------------------------------------------------------===//
+
+namespace {
+#define GEN_PASS_REGISTRATION
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h.inc"
+}  // namespace
+
+void registerHALInlinePasses() {
+  // Generated.
+  registerPasses();
+
+  static PassPipelineRegistration<> transformPassPipeline(
+      "iree-hal-inline-static-transformation-pipeline",
+      "Runs the inline HAL dialect transformation pipeline",
+      [](OpPassManager &passManager) {
+        buildHALInlineStaticTransformPassPipeline(
+            passManager, TargetOptions::FromFlags::get());
+      });
+}
+
+}  // namespace Inline
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h
new file mode 100644
index 0000000..b1247fd
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h
@@ -0,0 +1,63 @@
+// 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_DIALECT_MODULES_HAL_INLINE_TRANSFORMS_PASSES_H_
+#define IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_TRANSFORMS_PASSES_H_
+
+#include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineOps.h"
+#include "llvm/ADT/StringMap.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Pass/PassManager.h"
+#include "mlir/Support/LLVM.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+namespace Inline {
+
+//===----------------------------------------------------------------------===//
+// Helpers
+//===----------------------------------------------------------------------===//
+
+// Adds a set of passes to the given pass manager that run the required
+// HALInline transforms in the canonical order.
+//
+// Most translation code should prefer to use this instead of manually adding
+// the passes themselves to ensure that expected pass ordering is observed.
+//
+// The expected usage is:
+//   <run conversion from TF/HLO/etc to flow>
+//   buildHALInlineTransformPassPipeline & run
+//   <serialize VM module>
+void buildHALInlineStaticTransformPassPipeline(
+    OpPassManager &passManager, const TargetOptions &targetOptions);
+
+//===----------------------------------------------------------------------===//
+// Passes
+//===----------------------------------------------------------------------===//
+
+// Inlines translated executable functions into the host program.
+std::unique_ptr<OperationPass<mlir::ModuleOp>> createInlineExecutablesPass();
+
+// Converts from the stream dialect into the hal_inline dialect.
+std::unique_ptr<OperationPass<mlir::ModuleOp>> createConversionPass();
+
+//===----------------------------------------------------------------------===//
+// Register all Passes
+//===----------------------------------------------------------------------===//
+
+void registerHALInlinePasses();
+
+}  // namespace Inline
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_MODULES_HAL_INLINE_TRANSFORMS_PASSES_H_
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.td b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.td
new file mode 100644
index 0000000..60d2cb3
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.td
@@ -0,0 +1,22 @@
+// 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_MODULES_HAL_INLINE_PASSES
+#define IREE_MODULES_HAL_INLINE_PASSES
+
+include "mlir/Pass/PassBase.td"
+
+def Conversion : Pass<"iree-hal-inline-conversion", "mlir::ModuleOp"> {
+  let summary = "Converts from various dialects to the HAL inline dialect";
+  let constructor = "mlir::iree_compiler::IREE::HAL::Inline::createConversionPass()";
+}
+
+def InlineExecutables : Pass<"iree-hal-inline-executables", "mlir::ModuleOp"> {
+  let summary = "Inlines translated executable functions into the host program";
+  let constructor = "mlir::iree_compiler::IREE::HAL::Inline::createInlineExecutablesPass()";
+}
+
+#endif  // IREE_MODULES_HAL_INLINE_PASSES
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/BUILD b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/BUILD
new file mode 100644
index 0000000..a0e6f73
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/BUILD
@@ -0,0 +1,28 @@
+# 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
+
+load("//build_tools/bazel:iree_lit_test.bzl", "iree_lit_test_suite")
+load("//build_tools/bazel:enforce_glob.bzl", "enforce_glob")
+
+package(
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_lit_test_suite(
+    name = "lit",
+    srcs = enforce_glob(
+        [
+            "inline_executables.mlir",
+        ],
+        include = ["*.mlir"],
+    ),
+    cfg = "//compiler:lit.cfg.py",
+    tools = [
+        "//tools:iree-opt",
+        "@llvm-project//llvm:FileCheck",
+    ],
+)
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/CMakeLists.txt
new file mode 100644
index 0000000..f32aeb3
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/CMakeLists.txt
@@ -0,0 +1,23 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/BUILD  #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_lit_test_suite(
+  NAME
+    lit
+  SRCS
+    "inline_executables.mlir"
+  TOOLS
+    FileCheck
+    iree-opt
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/inline_executables.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/inline_executables.mlir
new file mode 100644
index 0000000..74dea66
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms/test/inline_executables.mlir
@@ -0,0 +1,184 @@
+// RUN: iree-opt --split-input-file --iree-hal-inline-executables %s | FileCheck %s
+
+// Tests that exported dispatch functions get placed into the module with
+// wrapper functions that perform the dispatch and all dispatch sites are tagged
+// with the wrapper function.
+
+// CHECK-NOT: hal.executable
+hal.executable private @ex {
+  hal.executable.variant public @vmvx_ir, target = <"vmvx-inline", "vmvx-ir"> {
+    hal.executable.export public @dispatch_0 ordinal(0) layout(
+         #hal.executable.layout<push_constants = 2,
+                                sets = [
+                                  <0, bindings = [
+                                    <0, storage_buffer>,
+                                    <1, storage_buffer>,
+                                    <2, storage_buffer>
+                                  ]>
+                                ]>) {
+    ^bb0(%arg0: !hal.device, %workload_x: index, %workload_y: index):
+      %count_x = affine.apply affine_map<()[s0] -> (s0 ceildiv 4)>()[%workload_x]
+      %count_y = affine.apply affine_map<()[s0] -> (s0 ceildiv 4)>()[%workload_y]
+      %count_z = arith.constant 1 : index
+      hal.return %count_x, %count_y, %count_z : index, index, index
+    }
+    builtin.module {
+      util.global private @global_constant : !util.buffer
+      util.initializer {
+        %buffer_cst = util.buffer.constant : !util.buffer = dense<[1, 2, 3, 4, 5]> : tensor<5xi32>
+        util.global.store %buffer_cst, @global_constant : !util.buffer
+        util.initializer.return
+      }
+      func.func @dispatch_0(
+          %local_memory: !util.buffer,
+          %constants: !util.buffer,
+          %bindings: !util.list<!util.buffer>,
+          %workgroup_x: index, %workgroup_y: index, %workgroup_z: index,
+          %workgroup_size_x: index, %workgroup_size_y: index, %workgroup_size_z: index,
+          %workgroup_count_x: index, %workgroup_count_y: index, %workgroup_count_z: index) {
+        // Unpack push constants:
+        %constants_size = util.buffer.size %constants : !util.buffer
+        %constant1_offset = arith.constant 4 : index
+        %constant1_i32 = util.buffer.load %constants[%constant1_offset] : !util.buffer{%constants_size} -> i32
+        %constant1_f32 = arith.sitofp %constant1_i32 : i32 to f32
+
+        // Unpack buffer bindings:
+        %c0 = arith.constant 0 : index
+        %buffer0 = util.list.get %bindings[%c0] : !util.list<!util.buffer>
+        %c1 = arith.constant 1 : index
+        %buffer1 = util.list.get %bindings[%c1] : !util.list<!util.buffer>
+        %c2 = arith.constant 2 : index
+        %buffer2 = util.list.get %bindings[%c2] : !util.list<!util.buffer>
+        %buffer0_size = util.buffer.size %buffer0 : !util.buffer
+        %buffer1_size = util.buffer.size %buffer1 : !util.buffer
+        %buffer2_size = util.buffer.size %buffer2 : !util.buffer
+
+        // Test for global constants:
+        %global_constant = util.global.load @global_constant : !util.buffer
+        util.do_not_optimize(%global_constant) : !util.buffer
+
+        %c4 = arith.constant 4 : index
+        scf.for %i = %c0 to %workgroup_x step %c1 {
+          %idx = arith.muli %i, %c4 : index
+          %lhs = util.buffer.load %buffer0[%idx] : !util.buffer{%buffer0_size} -> f32
+          %rhs = util.buffer.load %buffer1[%idx] : !util.buffer{%buffer1_size} -> f32
+          %mul = arith.mulf %lhs, %rhs : f32
+          %scaled = arith.mulf %mul, %constant1_f32 : f32
+          util.buffer.store %scaled, %buffer2[%idx] : f32 -> !util.buffer{%buffer2_size}
+        }
+        return
+      }
+    }
+  }
+}
+
+// Ensures that we properly rename the globals we inline:
+util.global private  @global_constant : i32
+
+// CHECK: util.global private @global_constant_0 : !util.buffer
+// CHECK: util.initializer
+// CHECK:   %[[CONSTANT:.+]] = util.buffer.constant
+// CHECK:   util.global.store %[[CONSTANT]], @global_constant
+
+// Ensures that we properly rename the dispatch function we inline:
+func.func private @dispatch_0()
+
+// CHECK-LABEL: func private @dispatch_0_0
+// CHECK-SAME: (%[[LOCAL_MEMORY:.+]]: !util.buffer, %[[CONSTANT0:.+]]: i32, %[[CONSTANT1:.+]]: i32,
+// CHECK-SAME:  %[[BINDING0:.+]]: !util.buffer, %[[BINDING1:.+]]: !util.buffer, %[[BINDING2:.+]]: !util.buffer,
+// CHECK-SAME:  %[[X:[a-z0-9]+]]: index, %[[Y:[a-z0-9]+]]: index, %[[Z:[a-z0-9]+]]: index,
+// CHECK-SAME:  %[[SIZE_XYZ:[a-z0-9]+]]: index, %[[SIZE_XYZ:[a-z0-9]+]]: index, %[[SIZE_XYZ:[a-z0-9]+]]: index,
+// CHECK-SAME:  %[[COUNT_X:[a-z0-9]+]]: index, %[[COUNT_Y:[a-z0-9]+]]: index, %[[COUNT_Z:[a-z0-9]+]]: index)
+
+// Push constant rewritten to use args:
+// CHECK: %[[CONSTANT1_F32:.+]] = arith.sitofp %[[CONSTANT1]] : i32 to f32
+
+// Bindings get changed to use args:
+// CHECK: %[[BINDING0_SIZE:.+]] = util.buffer.size %[[BINDING0]]
+// CHECK: %[[BINDING1_SIZE:.+]] = util.buffer.size %[[BINDING1]]
+// CHECK: %[[BINDING2_SIZE:.+]] = util.buffer.size %[[BINDING2]]
+
+// Globals get carried across:
+// CHECK: %[[GLOBAL_CONSTANT:.+]] = util.global.load @global_constant_0 : !util.buffer
+// CHECK: util.do_not_optimize(%[[GLOBAL_CONSTANT]])
+
+// CHECK: scf.for %[[ELEMENT_INDEX:.+]] = %c0 to %[[X]]
+// CHECK:   %[[ELEMENT_OFFSET:.+]] = arith.muli %[[ELEMENT_INDEX]]
+// CHECK:   %[[LHS:.+]] = util.buffer.load %[[BINDING0]][%[[ELEMENT_OFFSET]]] : !util.buffer{%[[BINDING0_SIZE]]} -> f32
+// CHECK:   %[[RHS:.+]] = util.buffer.load %[[BINDING1]][%[[ELEMENT_OFFSET]]] : !util.buffer{%[[BINDING1_SIZE]]} -> f32
+// CHECK:   %[[MUL:.+]] = arith.mulf %[[LHS]], %[[RHS]] : f32
+// CHECK:   %[[SCALED:.+]] = arith.mulf %[[MUL]], %[[CONSTANT1_F32]] : f32
+// CHECK:   util.buffer.store %[[SCALED]], %[[BINDING2]][%[[ELEMENT_OFFSET]]] : f32 -> !util.buffer{%[[BINDING2_SIZE]]}
+// CHECK: return
+
+// CHECK-LABEL: func private @__dispatch_ex_dispatch_0
+// CHECK-SAME: (%[[WORKLOAD_X:.+]]: index, %[[WORKLOAD_Y:.+]]: index, %[[CONSTANT0:.+]]: i32, %[[CONSTANT1:.+]]: i32,
+// CHECK-SAME:  %[[BINDING0:.+]]: !util.buffer, %[[BINDING1:.+]]: !util.buffer, %[[BINDING2:.+]]: !util.buffer,
+// CHECK-SAME:  %[[OFFSET0:[a-z0-9]+]]: index, %[[OFFSET1:[a-z0-9]+]]: index, %[[OFFSET2:[a-z0-9]+]]: index,
+// CHECK-SAME:  %[[LENGTH0:.+]]: index, %[[LENGTH1:.+]]: index, %[[LENGTH2:.+]]: index)
+
+// Inlined workgroup count calculation from the export op:
+// CHECK:   %[[COUNT_X:.+]] = affine.apply {{.+}}[%[[WORKLOAD_X]]]
+// CHECK:   %[[COUNT_Y:.+]] = affine.apply {{.+}}[%[[WORKLOAD_Y]]]
+// CHECK:   %[[COUNT_Z:.+]] = arith.constant 1
+
+// Local workgroup memory not currently used:
+// CHECK:   %[[LOCAL_MEMORY:.+]] = util.null : !util.buffer
+
+// Binding subspans as specified on the dispatch:
+// CHECK:   %[[BINDING0_SIZE:.+]] = util.buffer.size %[[BINDING0]]
+// CHECK:   %[[BINDING0_SPAN:.+]] = util.buffer.subspan %[[BINDING0]][%[[OFFSET0]]] : !util.buffer{%[[BINDING0_SIZE]]} -> !util.buffer{%[[LENGTH0]]}
+// CHECK:   %[[BINDING1_SIZE:.+]] = util.buffer.size %[[BINDING1]]
+// CHECK:   %[[BINDING1_SPAN:.+]] = util.buffer.subspan %[[BINDING1]][%[[OFFSET1]]] : !util.buffer{%[[BINDING1_SIZE]]} -> !util.buffer{%[[LENGTH1]]}
+// CHECK:   %[[BINDING2_SIZE:.+]] = util.buffer.size %[[BINDING2]]
+// CHECK:   %[[BINDING2_SPAN:.+]] = util.buffer.subspan %[[BINDING2]][%[[OFFSET2]]] : !util.buffer{%[[BINDING2_SIZE]]} -> !util.buffer{%[[LENGTH2]]}
+
+// Workgroup XYZ loop:
+// CHECK:   %[[SIZE_XYZ:.+]] = arith.constant 1
+// CHECK:   scf.for %[[Z:.+]] = %c0 to %[[COUNT_Z]]
+// CHECK:     scf.for %[[Y:.+]] = %c0 to %[[COUNT_Y]]
+// CHECK:       scf.for %[[X:.+]] = %c0 to %[[COUNT_X]]
+// CHECK:          func.call @dispatch_0_0(
+// CHECK-SAME:         %[[LOCAL_MEMORY]],
+// CHECK-SAME:         %[[CONSTANT0]], %[[CONSTANT1]],
+// CHECK-SAME:         %[[BINDING0_SPAN]], %[[BINDING1_SPAN]], %[[BINDING2_SPAN]],
+// CHECK-SAME:         %[[X]], %[[Y]], %[[Z]],
+// CHECK-SAME:         %[[SIZE_XYZ]], %[[SIZE_XYZ]], %[[SIZE_XYZ]],
+// CHECK-SAME:         %[[COUNT_X]], %[[COUNT_Y]], %[[COUNT_Z]])
+// CHECK:   return
+
+// CHECK-LABEL: @dispatch0
+// CHECK-SAME: (%[[RESOURCE0:.+]]: !stream.resource<constant>,
+// CHECK-SAME:  %[[RESOURCE1:.+]]: !stream.resource<transient>,
+// CHECK-SAME:  %[[RESOURCE2:.+]]: !stream.resource<external>)
+func.func private @dispatch0(%resource0: !stream.resource<constant>, %resource1: !stream.resource<transient>, %resource2: !stream.resource<external>) {
+  %workload_x = arith.constant 1000 : index
+  %workload_y = arith.constant 1001 : index
+  %constant0 = arith.constant 4 : i32
+  %constant1 = arith.constant 5 : i32
+  %binding0_offset = arith.constant 200 : index
+  %binding0_length = arith.constant 128 : index
+  %binding1_offset = arith.constant 300 : index
+  %binding1_length = arith.constant 256 : index
+  %binding2_offset = arith.constant 400 : index
+  %binding2_length = arith.constant 512 : index
+  %resource_size = arith.constant 1024 : index
+  %0 = stream.cmd.execute with(%resource0 as %resource0_inner: !stream.resource<constant>{%resource_size},
+                               %resource1 as %resource1_inner: !stream.resource<transient>{%resource_size},
+                               %resource2 as %resource2_inner: !stream.resource<external>{%resource_size}) {
+    // CHECK: stream.cmd.dispatch
+    // CHECK: hal_inline.target = @__dispatch_ex_dispatch_0
+    stream.cmd.dispatch @ex::@dispatch_0[%workload_x, %workload_y](%constant0, %constant1 : i32, i32) {
+      ro %resource0_inner[%binding0_offset for %binding0_length] : !stream.resource<constant>{%resource_size},
+      ro %resource1_inner[%binding1_offset for %binding1_length] : !stream.resource<transient>{%resource_size},
+      wo %resource2_inner[%binding2_offset for %binding2_length] : !stream.resource<external>{%resource_size}
+    } attributes {
+      hal.interface.bindings = [
+        #hal.interface.binding<0, 0>,
+        #hal.interface.binding<0, 1>,
+        #hal.interface.binding<0, 2>
+      ]
+    }
+  } => !stream.timepoint
+  return
+}
diff --git a/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/hal_inline.imports.mlir b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/hal_inline.imports.mlir
new file mode 100644
index 0000000..e9ee06d
--- /dev/null
+++ b/compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/hal_inline.imports.mlir
@@ -0,0 +1,131 @@
+// IREE Inline Hardware Abstraction Layer (HAL) runtime module imports.
+// This is only used to provide ABI compatibility with the full HAL module and
+// user programs that use !hal.buffer/!hal.buffer_view as IO.
+//
+// This is embedded in the compiler binary and inserted into any module
+// containing inline HAL dialect ops (hal_inline.*) that is lowered to the VM
+// dialect.
+vm.module @hal_inline {
+
+//===----------------------------------------------------------------------===//
+// iree_hal_buffer_t
+//===----------------------------------------------------------------------===//
+
+// Allocates an empty buffer.
+vm.import @buffer.allocate(
+  %minimum_alignment : i32,
+  %allocation_size : i64
+) -> (!vm.ref<!hal.buffer>, !vm.buffer)
+attributes {nosideeffects}
+
+// Allocates a buffer with an initial value provided by a VM byte buffer.
+vm.import @buffer.allocate.initialized(
+  %minimum_alignment : i32,
+  %source : !vm.buffer,
+  %offset : i64,
+  %length : i64
+) -> (!vm.ref<!hal.buffer>, !vm.buffer)
+attributes {nosideeffects}
+
+// Wraps a VM byte buffer in a HAL buffer.
+vm.import @buffer.wrap(
+  %source : !vm.buffer,
+  %offset : i64,
+  %length : i64
+) -> !vm.ref<!hal.buffer>
+attributes {nosideeffects}
+
+// Returns a reference to a subspan of the buffer.
+vm.import @buffer.subspan(
+  %source_buffer : !vm.ref<!hal.buffer>,
+  %source_offset : i64,
+  %length : i64
+) -> !vm.ref<!hal.buffer>
+attributes {nosideeffects}
+
+// TODO(benvanik): make storage return length and remove dedicated length?
+
+// Returns the byte length of the buffer (may be less than total allocation).
+vm.import @buffer.length(
+  %buffer : !vm.ref<!hal.buffer>
+) -> i64
+attributes {nosideeffects}
+
+// Returns a mapping to the underlying storage of the buffer sliced to the
+// logical subspan of the HAL buffer.
+vm.import @buffer.storage(
+  %buffer : !vm.ref<!hal.buffer>
+) -> !vm.buffer
+attributes {nosideeffects}
+
+//===----------------------------------------------------------------------===//
+// iree_hal_buffer_view_t
+//===----------------------------------------------------------------------===//
+
+// Creates a reference to a buffer with a particular shape and element type.
+vm.import @buffer_view.create(
+  %buffer : !vm.ref<!hal.buffer>,
+  %element_type : i32,
+  %encoding_type : i32,
+  %shape : i64 ...
+) -> !vm.ref<!hal.buffer_view>
+attributes {nosideeffects}
+
+// Asserts a buffer view matches the given tensor encoding and shape.
+vm.import @buffer_view.assert(
+  %buffer_view : !vm.ref<!hal.buffer_view>,
+  %message : !vm.buffer,
+  %element_type : i32,
+  %encoding_type : i32,
+  %shape : i64 ...
+)
+
+// Returns the backing buffer of the buffer view.
+vm.import @buffer_view.buffer(
+  %buffer_view : !vm.ref<!hal.buffer_view>
+) -> !vm.ref<!hal.buffer>
+attributes {nosideeffects}
+
+// Returns the element type of the buffer view.
+vm.import @buffer_view.element_type(
+  %buffer_view : !vm.ref<!hal.buffer_view>,
+) -> i32
+attributes {nosideeffects}
+
+// Returns the encoding type of the buffer view.
+vm.import @buffer_view.encoding_type(
+  %buffer_view : !vm.ref<!hal.buffer_view>,
+) -> i32
+attributes {nosideeffects}
+
+// Returns the rank of the buffer view.
+vm.import @buffer_view.rank(
+  %buffer_view : !vm.ref<!hal.buffer_view>,
+) -> i32
+attributes {nosideeffects}
+
+// Returns the value of the given dimension.
+vm.import @buffer_view.dim(
+  %buffer_view : !vm.ref<!hal.buffer_view>,
+  %index : i32
+) -> i64
+attributes {nosideeffects}
+
+// Prints out the content of buffer views.
+vm.import @buffer_view.trace(
+  %key : !vm.buffer,
+  %operands : !vm.ref<!hal.buffer_view> ...
+)
+
+//===----------------------------------------------------------------------===//
+// iree_hal_device_t
+//===----------------------------------------------------------------------===//
+
+// Returns a tuple of (ok, value) for the given configuration key.
+vm.import @device.query.i64(
+  %category : !vm.buffer,
+  %key : !vm.buffer
+) -> (i32, i64)
+attributes {nosideeffects}
+
+}  // module
diff --git a/compiler/src/iree/compiler/Dialect/VMVX/Conversion/HALToVMVX/ConvertHALToVMVX.cpp b/compiler/src/iree/compiler/Dialect/VMVX/Conversion/HALToVMVX/ConvertHALToVMVX.cpp
index 7ac2522..11ccd4b 100644
--- a/compiler/src/iree/compiler/Dialect/VMVX/Conversion/HALToVMVX/ConvertHALToVMVX.cpp
+++ b/compiler/src/iree/compiler/Dialect/VMVX/Conversion/HALToVMVX/ConvertHALToVMVX.cpp
@@ -176,10 +176,13 @@
     auto constantsArg = op->getParentOfType<mlir::func::FuncOp>().getArgument(
         kEntryArgConstants);
     assert(constantsArg && "entry point not conforming to requirements");
+    // HACK: we could find the total push constant count and avoid this size op
+    // but it'd require walking all the way up to the hal.executable export.
     auto constantsSize =
         rewriter.create<IREE::Util::BufferSizeOp>(op.getLoc(), constantsArg);
     auto resultType = getTypeConverter()->convertType(op.getResult().getType());
 
+    // Index -> byte offset.
     auto constantIndex = rewriter.createOrFold<arith::ConstantIndexOp>(
         op.getLoc(), op.getIndex().getZExtValue());
     auto elementSize =
@@ -254,7 +257,7 @@
             .getResult();
 
     if (op.getByteOffset() && !matchPattern(op.getByteOffset(), m_Zero())) {
-      // Offsetted binding: replace with a BufferSpan.
+      // Offsetted binding: replace with a BufferSubspanOp.
       Value sourceSize = rewriter.createOrFold<IREE::Util::BufferSizeOp>(
           op.getLoc(), sourceBuffer);
 
diff --git a/compiler/src/iree/compiler/Pipelines/BUILD b/compiler/src/iree/compiler/Pipelines/BUILD
index da71ce4..48030f7 100644
--- a/compiler/src/iree/compiler/Pipelines/BUILD
+++ b/compiler/src/iree/compiler/Pipelines/BUILD
@@ -39,6 +39,7 @@
         "//compiler/src/iree/compiler/Dialect/Flow/Transforms",
         "//compiler/src/iree/compiler/Dialect/HAL/Conversion/HALToVM",
         "//compiler/src/iree/compiler/Dialect/HAL/Transforms",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms",
         "//compiler/src/iree/compiler/Dialect/Stream/Transforms",
         "//compiler/src/iree/compiler/Dialect/Util/Transforms",
         "//compiler/src/iree/compiler/Dialect/VM/Conversion",
diff --git a/compiler/src/iree/compiler/Pipelines/CMakeLists.txt b/compiler/src/iree/compiler/Pipelines/CMakeLists.txt
index f91ccca..824566f 100644
--- a/compiler/src/iree/compiler/Pipelines/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Pipelines/CMakeLists.txt
@@ -48,6 +48,7 @@
     iree::compiler::Dialect::Flow::Transforms
     iree::compiler::Dialect::HAL::Conversion::HALToVM
     iree::compiler::Dialect::HAL::Transforms
+    iree::compiler::Dialect::Modules::HAL::Inline::Transforms
     iree::compiler::Dialect::Stream::Transforms
     iree::compiler::Dialect::Util::Transforms
     iree::compiler::Dialect::VM::Conversion
diff --git a/compiler/src/iree/compiler/Pipelines/Options.cpp b/compiler/src/iree/compiler/Pipelines/Options.cpp
index a7825ff..1c68eb2 100644
--- a/compiler/src/iree/compiler/Pipelines/Options.cpp
+++ b/compiler/src/iree/compiler/Pipelines/Options.cpp
@@ -97,7 +97,10 @@
                      "internally but exporting functions as if synchronous."),
           clEnumValN(ExecutionModel::AsyncExternal, "async-external",
                      "Full HAL using asynchronous host/device execution both "
-                     "internally and externally.")),
+                     "internally and externally."),
+          clEnumValN(ExecutionModel::InlineStatic, "inline-static",
+                     "Inline host-local in-process execution with executable "
+                     "code statically linked into the host program.")),
       llvm::cl::cat(category));
 
   binder.opt<DumpOutputFormat>(
diff --git a/compiler/src/iree/compiler/Pipelines/Options.h b/compiler/src/iree/compiler/Pipelines/Options.h
index e048c51..405ee84 100644
--- a/compiler/src/iree/compiler/Pipelines/Options.h
+++ b/compiler/src/iree/compiler/Pipelines/Options.h
@@ -88,6 +88,10 @@
     // Full HAL using asynchronous host/device execution both internally and
     // externally.
     AsyncExternal = 2,
+    // Inline host-local in-process execution with executable code statically
+    // linked into the host program.
+    // (Currently) only supports the `vmvx-inline` HAL target backend.
+    InlineStatic = 3,
   };
   // Program execution model specifying scheduling behavior.
   ExecutionModel executionModel = ExecutionModel::AsyncInternal;
diff --git a/compiler/src/iree/compiler/Pipelines/Pipelines.cpp b/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
index 6bea87a..11e3c0e 100644
--- a/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
+++ b/compiler/src/iree/compiler/Pipelines/Pipelines.cpp
@@ -10,6 +10,7 @@
 #include "iree/compiler/Bindings/TFLite/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Flow/Transforms/Passes.h"
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Stream/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Util/Transforms/Passes.h"
 #include "iree/compiler/Dialect/VM/Transforms/Passes.h"
@@ -127,6 +128,10 @@
     case SchedulingOptions::ExecutionModel::AsyncExternal:
       IREE::HAL::buildHALTransformPassPipeline(passManager, executableOptions);
       break;
+    case SchedulingOptions::ExecutionModel::InlineStatic:
+      IREE::HAL::Inline::buildHALInlineStaticTransformPassPipeline(
+          passManager, executableOptions);
+      break;
   }
 
   IREE::VM::buildVMTransformPassPipeline(passManager, targetOptions);
diff --git a/compiler/src/iree/compiler/Tools/BUILD b/compiler/src/iree/compiler/Tools/BUILD
index 0ff9e15..2bff338 100644
--- a/compiler/src/iree/compiler/Tools/BUILD
+++ b/compiler/src/iree/compiler/Tools/BUILD
@@ -50,6 +50,8 @@
         "//compiler/src/iree/compiler/Dialect/Flow/Transforms",
         "//compiler/src/iree/compiler/Dialect/HAL/IR:HALDialect",
         "//compiler/src/iree/compiler/Dialect/HAL/Transforms",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/IR:HALInlineDialect",
+        "//compiler/src/iree/compiler/Dialect/Modules/HAL/Inline/Transforms",
         "//compiler/src/iree/compiler/Dialect/Stream/IR",
         "//compiler/src/iree/compiler/Dialect/Stream/Transforms",
         "//compiler/src/iree/compiler/Dialect/Util/IR",
diff --git a/compiler/src/iree/compiler/Tools/CMakeLists.txt b/compiler/src/iree/compiler/Tools/CMakeLists.txt
index 531ddaa..43da3e5 100644
--- a/compiler/src/iree/compiler/Tools/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Tools/CMakeLists.txt
@@ -93,8 +93,8 @@
     iree::compiler::Dialect::Flow::Transforms
     iree::compiler::Dialect::HAL::IR::HALDialect
     iree::compiler::Dialect::HAL::Transforms
-    iree::compiler::Dialect::VMVX::IR::VMVXDialect
-    iree::compiler::Dialect::VMVX::Transforms
+    iree::compiler::Dialect::Modules::HAL::Inline::IR::HALInlineDialect
+    iree::compiler::Dialect::Modules::HAL::Inline::Transforms
     iree::compiler::Dialect::Stream::IR
     iree::compiler::Dialect::Stream::Transforms
     iree::compiler::Dialect::Util::IR
@@ -104,6 +104,8 @@
     iree::compiler::Dialect::VM::IR
     iree::compiler::Dialect::VM::Target::init_targets
     iree::compiler::Dialect::VM::Transforms
+    iree::compiler::Dialect::VMVX::IR::VMVXDialect
+    iree::compiler::Dialect::VMVX::Transforms
     iree::compiler::Dialect::Vulkan::IR
     iree::compiler::ConstEval
     iree::compiler::Pipelines
diff --git a/compiler/src/iree/compiler/Tools/init_iree_dialects.h b/compiler/src/iree/compiler/Tools/init_iree_dialects.h
index b2c1308..5cfa88e 100644
--- a/compiler/src/iree/compiler/Tools/init_iree_dialects.h
+++ b/compiler/src/iree/compiler/Tools/init_iree_dialects.h
@@ -21,6 +21,7 @@
 #include "iree/compiler/Codegen/Interfaces/Interfaces.h"
 #include "iree/compiler/Dialect/Flow/IR/FlowDialect.h"
 #include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/IR/HALInlineDialect.h"
 #include "iree/compiler/Dialect/Stream/IR/StreamDialect.h"
 #include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
 #include "iree/compiler/Dialect/Util/IR/UtilExternalModels.h"
@@ -38,6 +39,7 @@
   registry.insert<IREE::Codegen::IREECodegenDialect,
                   IREE::Flow::FlowDialect,
                   IREE::HAL::HALDialect,
+                  IREE::HAL::Inline::HALInlineDialect,
                   IREE::LinalgExt::IREELinalgExtDialect,
                   mlir::linalg::transform::LinalgTransformDialect,
                   IREE::Stream::StreamDialect,
diff --git a/compiler/src/iree/compiler/Tools/init_iree_passes.h b/compiler/src/iree/compiler/Tools/init_iree_passes.h
index 8192cac..a01be67 100644
--- a/compiler/src/iree/compiler/Tools/init_iree_passes.h
+++ b/compiler/src/iree/compiler/Tools/init_iree_passes.h
@@ -20,6 +20,7 @@
 #include "iree/compiler/ConstEval/Passes.h"
 #include "iree/compiler/Dialect/Flow/Transforms/Passes.h"
 #include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
+#include "iree/compiler/Dialect/Modules/HAL/Inline/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Stream/Transforms/Passes.h"
 #include "iree/compiler/Dialect/Util/Transforms/Passes.h"
 #include "iree/compiler/Dialect/VM/Analysis/TestPasses.h"
@@ -46,6 +47,7 @@
   ConstEval::registerConstEvalPasses();
   IREE::Flow::registerFlowPasses();
   IREE::HAL::registerHALPasses();
+  IREE::HAL::Inline::registerHALInlinePasses();
   IREE::LinalgExt::registerPasses();
   IREE::Stream::registerStreamPasses();
   IREE::Util::registerTransformPasses();
diff --git a/runtime/src/iree/modules/hal/inline/BUILD b/runtime/src/iree/modules/hal/inline/BUILD
new file mode 100644
index 0000000..9b0994e
--- /dev/null
+++ b/runtime/src/iree/modules/hal/inline/BUILD
@@ -0,0 +1,34 @@
+# 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
+
+load("//build_tools/bazel:build_defs.oss.bzl", "iree_runtime_cc_library")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_runtime_cc_library(
+    name = "inline",
+    srcs = [
+        "module.c",
+    ],
+    hdrs = [
+        "module.h",
+    ],
+    textual_hdrs = [
+        "exports.inl",
+    ],
+    deps = [
+        "//runtime/src/iree/base",
+        "//runtime/src/iree/base:tracing",
+        "//runtime/src/iree/hal",
+        "//runtime/src/iree/modules/hal:types",
+        "//runtime/src/iree/modules/hal/utils:buffer_diagnostics",
+        "//runtime/src/iree/vm",
+    ],
+)
diff --git a/runtime/src/iree/modules/hal/inline/CMakeLists.txt b/runtime/src/iree/modules/hal/inline/CMakeLists.txt
new file mode 100644
index 0000000..64022f8
--- /dev/null
+++ b/runtime/src/iree/modules/hal/inline/CMakeLists.txt
@@ -0,0 +1,32 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# runtime/src/iree/modules/hal/inline/BUILD                                    #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+iree_add_all_subdirs()
+
+iree_cc_library(
+  NAME
+    inline
+  HDRS
+    "module.h"
+  TEXTUAL_HDRS
+    "exports.inl"
+  SRCS
+    "module.c"
+  DEPS
+    iree::base
+    iree::base::tracing
+    iree::hal
+    iree::modules::hal::types
+    iree::modules::hal::utils::buffer_diagnostics
+    iree::vm
+  PUBLIC
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/runtime/src/iree/modules/hal/inline/exports.inl b/runtime/src/iree/modules/hal/inline/exports.inl
new file mode 100644
index 0000000..40f80ce
--- /dev/null
+++ b/runtime/src/iree/modules/hal/inline/exports.inl
@@ -0,0 +1,45 @@
+// 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
+
+//===----------------------------------------------------------------------===//
+//
+//         ██     ██  █████  ██████  ███    ██ ██ ███    ██  ██████
+//         ██     ██ ██   ██ ██   ██ ████   ██ ██ ████   ██ ██
+//         ██  █  ██ ███████ ██████  ██ ██  ██ ██ ██ ██  ██ ██   ███
+//         ██ ███ ██ ██   ██ ██   ██ ██  ██ ██ ██ ██  ██ ██ ██    ██
+//          ███ ███  ██   ██ ██   ██ ██   ████ ██ ██   ████  ██████
+//
+//===----------------------------------------------------------------------===//
+//
+// This file will be auto generated from hal_inline.imports.mlir in the future;
+// for now it's modified by hand but with strict alphabetical sorting required.
+// The order of these functions must be sorted ascending by name in a way
+// compatible with iree_string_view_compare.
+//
+// Users are meant to `#define EXPORT_FN` to be able to access the information.
+// #define EXPORT_FN(name, target_fn, arg_type, ret_type)
+
+// clang-format off
+
+EXPORT_FN("buffer.allocate", iree_hal_inline_module_buffer_allocate, iI, rr)
+EXPORT_FN("buffer.allocate.initialized", iree_hal_inline_module_buffer_allocate_initialized, irII, rr)
+EXPORT_FN("buffer.length", iree_hal_inline_module_buffer_length, r, I)
+EXPORT_FN("buffer.storage", iree_hal_inline_module_buffer_storage, r, r)
+EXPORT_FN("buffer.subspan", iree_hal_inline_module_buffer_subspan, rII, r)
+EXPORT_FN("buffer.wrap", iree_hal_inline_module_buffer_wrap, rII, r)
+
+EXPORT_FN("buffer_view.assert", iree_hal_inline_module_buffer_view_assert, rriiCID, v)
+EXPORT_FN("buffer_view.buffer", iree_hal_inline_module_buffer_view_buffer, r, r)
+EXPORT_FN("buffer_view.create", iree_hal_inline_module_buffer_view_create, riiCID, r)
+EXPORT_FN("buffer_view.dim", iree_hal_inline_module_buffer_view_dim, ri, I)
+EXPORT_FN("buffer_view.element_type", iree_hal_inline_module_buffer_view_element_type, r, i)
+EXPORT_FN("buffer_view.encoding_type", iree_hal_inline_module_buffer_view_encoding_type, r, i)
+EXPORT_FN("buffer_view.rank", iree_hal_inline_module_buffer_view_rank, r, i)
+EXPORT_FN("buffer_view.trace", iree_hal_inline_module_buffer_view_trace, rCrD, v)
+
+EXPORT_FN("device.query.i64", iree_hal_inline_module_device_query_i64, rr, iI)
+
+// clang-format on
diff --git a/runtime/src/iree/modules/hal/inline/module.c b/runtime/src/iree/modules/hal/inline/module.c
new file mode 100644
index 0000000..65b460c
--- /dev/null
+++ b/runtime/src/iree/modules/hal/inline/module.c
@@ -0,0 +1,609 @@
+// 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/modules/hal/inline/module.h"
+
+#include "iree/base/api.h"
+#include "iree/base/tracing.h"
+#include "iree/hal/api.h"
+#include "iree/modules/hal/utils/buffer_diagnostics.h"
+#include "iree/vm/api.h"
+
+#define IREE_HAL_INLINE_MODULE_VERSION_0_0 0x00000000u
+#define IREE_HAL_INLINE_MODULE_VERSION_LATEST IREE_HAL_INLINE_MODULE_VERSION_0_0
+
+//===----------------------------------------------------------------------===//
+// iree_hal_inline_storage_buffer_t
+//===----------------------------------------------------------------------===//
+
+// Inlined VM buffer using a HAL buffer for storage.
+// This uses the reference counting of the embedded VM buffer
+// to track lifetime combined with a custom allocator to handle
+// cleaning up this wrapper when the VM buffer is no longer referenced.
+//
+// Since the HAL buffer is providing the storage and the VM buffer is just
+// pointing into it the critical thing this wrapper does is ensure the HAL
+// buffer always outlives the VM buffer.
+//
+// NOTE: this is allocated each storage query! The assumption is that the
+// returned buffer is long-lived (at least per-invocation). This is primarily
+// used to get the backing storage of a !hal.buffer that a user passes into an
+// invocation and the compiler should CSE such queries. Since users can provide
+// their own allocators they can decide if they want to pool small allocations
+// to bypass the system allocator. If we wanted to in here we could have a small
+// free list we maintained for this purpose at the cost of fixed memory
+// consumption. Note that the key requirement is that the returned VM buffer
+// may outlive the module so we can't use an arena that has module lifetime.
+typedef struct iree_hal_inline_storage_buffer_t {
+  // Allocator used to allocate this storage buffer.
+  iree_allocator_t host_allocator;
+  // HAL buffer backing this storage buffer.
+  // Retained for the lifetime of this instance so that the
+  // wrapped vm_buffer is always valid.
+  iree_hal_buffer_t* hal_buffer;
+  // Scoped mapping into the buffer. We could make it persistent but because
+  // we can trivially scope things having this extra information is cheap and
+  // useful for debugging.
+  iree_hal_buffer_mapping_t mapping;
+  // Inline initialized VM buffer wrapping the hal_buffer storage.
+  // This directly references the memory of the HAL buffer.
+  // The buffer has a custom allocator that calls back into this
+  // struct to deallocate the wrapper.
+  iree_vm_buffer_t vm_buffer;
+} iree_hal_inline_storage_buffer_t;
+
+static void iree_hal_inline_storage_buffer_destroy(
+    iree_hal_inline_storage_buffer_t* storage);
+
+static iree_status_t iree_hal_inline_storage_buffer_ctl(
+    void* self, iree_allocator_command_t command, const void* params,
+    void** inout_ptr) {
+  if (command != IREE_ALLOCATOR_COMMAND_FREE) {
+    return iree_make_status(
+        IREE_STATUS_FAILED_PRECONDITION,
+        "allocator can only be used for dropping the wrapper buffer");
+  }
+  iree_hal_inline_storage_buffer_t* storage =
+      (iree_hal_inline_storage_buffer_t*)self;
+  iree_hal_inline_storage_buffer_destroy(storage);
+  return iree_ok_status();
+}
+
+// Creates a VM buffer wrapper that directly references HAL buffer storage.
+// The returned |out_vm_buffer| lifetime will extend the HAL buffer lifetime.
+static iree_status_t iree_hal_inline_storage_buffer_create(
+    iree_hal_buffer_t* hal_buffer, iree_allocator_t host_allocator,
+    iree_vm_buffer_t** out_vm_buffer) {
+  IREE_ASSERT_ARGUMENT(hal_buffer);
+  IREE_ASSERT_ARGUMENT(out_vm_buffer);
+  *out_vm_buffer = NULL;
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  // Allocate zero-initialized storage wrapper.
+  iree_hal_inline_storage_buffer_t* storage = NULL;
+  IREE_RETURN_AND_END_ZONE_IF_ERROR(
+      z0, iree_allocator_malloc(host_allocator, sizeof(*storage),
+                                (void**)&storage));
+
+  // Map the HAL buffer into host-accessible memory. It almost always is but
+  // it's possible the buffer we were passed was allocated on a real device that
+  // requires mapping.
+  iree_status_t status = iree_hal_buffer_map_range(
+      hal_buffer, IREE_HAL_MAPPING_MODE_SCOPED, IREE_HAL_MEMORY_ACCESS_ANY, 0,
+      IREE_WHOLE_BUFFER, &storage->mapping);
+
+  // Initializes the VM buffer to reference the mapped memory.
+  // Since the VM buffer is what we pass back to the VM and gets reference
+  // counted we pass a custom allocator that lets us know when the VM (or
+  // user) is done with it.
+  if (iree_status_is_ok(status)) {
+    iree_allocator_t self_allocator = {
+        .self = storage,
+        .ctl = iree_hal_inline_storage_buffer_ctl,
+    };
+    iree_vm_buffer_initialize(
+        IREE_VM_BUFFER_ACCESS_ORIGIN_HOST | IREE_VM_BUFFER_ACCESS_MUTABLE,
+        storage->mapping.contents, self_allocator, &storage->vm_buffer);
+  }
+
+  if (iree_status_is_ok(status)) {
+    *out_vm_buffer = &storage->vm_buffer;
+  } else {
+    iree_hal_inline_storage_buffer_destroy(storage);
+  }
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
+static void iree_hal_inline_storage_buffer_destroy(
+    iree_hal_inline_storage_buffer_t* storage) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+  iree_allocator_t host_allocator = storage->host_allocator;
+  iree_hal_buffer_unmap_range(&storage->mapping);
+  iree_hal_buffer_release(storage->hal_buffer);
+  iree_allocator_free(host_allocator, storage);
+  IREE_TRACE_ZONE_END(z0);
+}
+
+//===----------------------------------------------------------------------===//
+// Module type definitions
+//===----------------------------------------------------------------------===//
+
+typedef struct iree_hal_inline_module_t {
+  iree_allocator_t host_allocator;
+  iree_hal_allocator_t* device_allocator;
+  iree_hal_inline_module_flags_t flags;
+  // TODO(benvanik): types.
+} iree_hal_inline_module_t;
+
+#define IREE_HAL_INLINE_MODULE_CAST(module)        \
+  (iree_hal_inline_module_t*)((uint8_t*)(module) + \
+                              iree_vm_native_module_size());
+
+typedef struct iree_hal_inline_module_state_t {
+  iree_allocator_t host_allocator;
+  iree_hal_allocator_t* device_allocator;
+  iree_hal_inline_module_flags_t flags;
+} iree_hal_inline_module_state_t;
+
+static void IREE_API_PTR iree_hal_inline_module_destroy(void* base_module) {
+  iree_hal_inline_module_t* module = IREE_HAL_INLINE_MODULE_CAST(base_module);
+  iree_hal_allocator_release(module->device_allocator);
+  module->device_allocator = NULL;
+}
+
+static iree_status_t IREE_API_PTR
+iree_hal_inline_module_alloc_state(void* self, iree_allocator_t host_allocator,
+                                   iree_vm_module_state_t** out_module_state) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  iree_hal_inline_module_t* module = IREE_HAL_INLINE_MODULE_CAST(self);
+  iree_hal_inline_module_state_t* state = NULL;
+  IREE_RETURN_AND_END_ZONE_IF_ERROR(
+      z0,
+      iree_allocator_malloc(host_allocator, sizeof(*state), (void**)&state));
+  memset(state, 0, sizeof(*state));
+  state->host_allocator = host_allocator;
+  state->device_allocator = module->device_allocator;
+  iree_hal_allocator_retain(state->device_allocator);
+  state->flags = module->flags;
+
+  *out_module_state = (iree_vm_module_state_t*)state;
+  IREE_TRACE_ZONE_END(z0);
+  return iree_ok_status();
+}
+
+static void IREE_API_PTR iree_hal_inline_module_free_state(
+    void* self, iree_vm_module_state_t* module_state) {
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  iree_hal_inline_module_state_t* state =
+      (iree_hal_inline_module_state_t*)module_state;
+  iree_hal_allocator_release(state->device_allocator);
+  state->device_allocator = NULL;
+  iree_allocator_free(state->host_allocator, state);
+
+  IREE_TRACE_ZONE_END(z0);
+}
+
+static iree_status_t IREE_API_PTR iree_hal_inline_module_notify(
+    void* self, iree_vm_module_state_t* module_state, iree_vm_signal_t signal) {
+  switch (signal) {
+    case IREE_VM_SIGNAL_SUSPEND:
+    case IREE_VM_SIGNAL_LOW_MEMORY:
+    default:
+      return iree_ok_status();
+  }
+}
+
+//===----------------------------------------------------------------------===//
+// Utilities
+//===----------------------------------------------------------------------===//
+
+// Casts a VM value to a C host size.
+static iree_host_size_t iree_hal_cast_host_size(int64_t value) {
+  // TODO(benvanik): make this return status and check for overflow if host
+  // size is 32-bits.
+  return (iree_host_size_t)value;
+}
+
+// Casts a VM value to a HAL device size.
+static iree_device_size_t iree_hal_cast_device_size(int64_t value) {
+  // TODO(benvanik): make this return status and check for overflow if device
+  // size is 32-bits.
+  return (iree_device_size_t)value;
+}
+
+//===----------------------------------------------------------------------===//
+// iree_hal_buffer_t
+//===----------------------------------------------------------------------===//
+
+static iree_status_t iree_hal_inline_module_buffer_allocate_with_storage(
+    iree_hal_allocator_t* device_allocator, iree_hal_buffer_params_t params,
+    iree_device_size_t allocation_size, iree_const_byte_span_t initial_data,
+    iree_allocator_t host_allocator, iree_hal_buffer_t** out_buffer,
+    iree_vm_buffer_t** out_storage) {
+  // We could optimize this to create both at the same time and avoid the extra
+  // storage allocation by having a custom iree_hal_buffer_t type or a way to
+  // allocate additional data in the iree_hal_buffer_params_t that we stashed
+  // the storage in. Today this is all intentionally simple and something we can
+  // change in the runtime without impacting the compiler/artifacts.
+
+  // Try allocating the buffer first
+  iree_hal_buffer_t* buffer = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_allocator_allocate_buffer(
+      device_allocator, params, allocation_size, initial_data, &buffer));
+
+  // Map and retain the HAL buffer and return a VM buffer that is usable as if
+  // it were a native iree_vm_buffer_t.
+  iree_vm_buffer_t* storage = NULL;
+  iree_status_t status =
+      iree_hal_inline_storage_buffer_create(buffer, host_allocator, &storage);
+  if (!iree_status_is_ok(status)) {
+    iree_hal_buffer_release(buffer);
+    return status;
+  }
+
+  *out_buffer = buffer;
+  *out_storage = storage;
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_allocate,  //
+                   iree_hal_inline_module_state_t,          //
+                   iI, rr) {
+  iree_device_size_t minimum_alignment = iree_hal_cast_device_size(args->i0);
+  iree_device_size_t allocation_size = iree_hal_cast_device_size(args->i1);
+
+  const iree_hal_buffer_params_t params = {
+      .usage = IREE_HAL_BUFFER_USAGE_TRANSFER |
+               IREE_HAL_BUFFER_USAGE_DISPATCH_STORAGE |
+               IREE_HAL_BUFFER_USAGE_MAPPING,
+      .access = IREE_HAL_MEMORY_ACCESS_ALL,
+      .type = IREE_HAL_MEMORY_TYPE_OPTIMAL_FOR_HOST,
+      .min_alignment = minimum_alignment,
+  };
+  iree_hal_buffer_t* buffer = NULL;
+  iree_vm_buffer_t* storage = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_inline_module_buffer_allocate_with_storage(
+      state->device_allocator, params, allocation_size,
+      iree_const_byte_span_empty(), state->host_allocator, &buffer, &storage));
+
+  rets->r0 = iree_hal_buffer_move_ref(buffer);
+  rets->r1 = iree_vm_buffer_move_ref(storage);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_allocate_initialized,  //
+                   iree_hal_inline_module_state_t,                      //
+                   irII, rr) {
+  iree_device_size_t minimum_alignment = iree_hal_cast_device_size(args->i0);
+  iree_vm_buffer_t* source_buffer = NULL;
+  IREE_RETURN_IF_ERROR(iree_vm_buffer_check_deref(args->r1, &source_buffer));
+  iree_device_size_t source_offset = iree_hal_cast_device_size(args->i2);
+  iree_device_size_t source_length = iree_hal_cast_device_size(args->i3);
+
+  iree_const_byte_span_t initial_data = iree_const_byte_span_empty();
+  IREE_RETURN_IF_ERROR(iree_vm_buffer_map_ro(source_buffer, source_offset,
+                                             source_length, 1, &initial_data));
+
+  const iree_hal_buffer_params_t params = {
+      .usage = IREE_HAL_BUFFER_USAGE_TRANSFER |
+               IREE_HAL_BUFFER_USAGE_DISPATCH_STORAGE |
+               IREE_HAL_BUFFER_USAGE_MAPPING,
+      .access = IREE_HAL_MEMORY_ACCESS_ALL,
+      .type = IREE_HAL_MEMORY_TYPE_OPTIMAL_FOR_HOST,
+      .min_alignment = minimum_alignment,
+  };
+  iree_hal_buffer_t* buffer = NULL;
+  iree_vm_buffer_t* storage = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_inline_module_buffer_allocate_with_storage(
+      state->device_allocator, params, source_length, initial_data,
+      state->host_allocator, &buffer, &storage));
+
+  rets->r0 = iree_hal_buffer_move_ref(buffer);
+  rets->r1 = iree_vm_buffer_move_ref(storage);
+  return iree_ok_status();
+
+  return iree_make_status(IREE_STATUS_UNIMPLEMENTED);
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_wrap,  //
+                   iree_hal_inline_module_state_t,      //
+                   rII, r) {
+  iree_vm_buffer_t* source_buffer = NULL;
+  IREE_RETURN_IF_ERROR(iree_vm_buffer_check_deref(args->r0, &source_buffer));
+  iree_device_size_t source_offset = iree_hal_cast_device_size(args->i1);
+  iree_device_size_t source_length = iree_hal_cast_device_size(args->i2);
+
+  // TODO(benvanik): implement buffer wrapping.
+  // We don't emit this on the compiler today but could if we wanted to return
+  // constants/variables from the program without copies.
+  //
+  // We could do this by having a custom iree_hal_buffer_t type that retains
+  // the vm buffer, like `iree_hal_external_vm_buffer_t`.
+  // We may then want to expose this wrap method on the public module API so
+  // that users can pass in buffers like this.
+  //
+  // hal_inline.buffer.storage would need to switch based on type and return
+  // the underlying wrapped vm.buffer.
+  (void)source_buffer;
+  (void)source_offset;
+  (void)source_length;
+
+  return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
+                          "vm->hal buffer wrapping not yet implemented");
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_subspan,  //
+                   iree_hal_inline_module_state_t,         //
+                   rII, r) {
+  iree_hal_buffer_t* source_buffer = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_buffer_check_deref(args->r0, &source_buffer));
+  iree_device_size_t source_offset = iree_hal_cast_device_size(args->i1);
+  iree_device_size_t length = iree_hal_cast_device_size(args->i2);
+
+  iree_hal_buffer_t* subspan_buffer = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_hal_buffer_subspan(source_buffer, source_offset, length,
+                              &subspan_buffer),
+      "invalid subspan of an existing buffer (source_offset=%" PRIdsz
+      ", length=%" PRIdsz ")",
+      source_offset, length);
+
+  rets->r0 = iree_hal_buffer_move_ref(subspan_buffer);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_length,  //
+                   iree_hal_inline_module_state_t,        //
+                   r, I) {
+  iree_hal_buffer_t* buffer = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_buffer_check_deref(args->r0, &buffer));
+  rets->i0 = (int64_t)iree_hal_buffer_byte_length(buffer);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_storage,  //
+                   iree_hal_inline_module_state_t,         //
+                   r, r) {
+  iree_hal_buffer_t* hal_buffer = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_buffer_check_deref(args->r0, &hal_buffer));
+
+  // Map and retain the HAL buffer and return a VM buffer that is usable as if
+  // it were a native iree_vm_buffer_t.
+  iree_vm_buffer_t* vm_buffer = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_inline_storage_buffer_create(
+      hal_buffer, state->host_allocator, &vm_buffer));
+
+  rets->r0 = iree_vm_buffer_move_ref(vm_buffer);
+  return iree_ok_status();
+}
+
+//===----------------------------------------------------------------------===//
+// iree_hal_buffer_view_t
+//===----------------------------------------------------------------------===//
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_view_create,  //
+                   iree_hal_inline_module_state_t,             //
+                   riiCID, r) {
+  iree_hal_buffer_t* source_buffer = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_buffer_check_deref(args->r0, &source_buffer));
+  iree_hal_element_type_t element_type = (iree_hal_element_type_t)args->i1;
+  iree_hal_encoding_type_t encoding_type = (iree_hal_encoding_type_t)args->i2;
+  iree_host_size_t shape_rank = 0;
+  iree_hal_dim_t* shape_dims = NULL;
+  // TODO(benvanik): avoid the cast/alloca if not required.
+  IREE_VM_ABI_VLA_STACK_CAST(args, a3_count, a3, iree_hal_dim_t, 128,
+                             &shape_rank, &shape_dims);
+  iree_hal_buffer_view_t* buffer_view = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_buffer_view_create(
+      source_buffer, shape_rank, shape_dims, element_type, encoding_type,
+      state->host_allocator, &buffer_view));
+  rets->r0 = iree_hal_buffer_view_move_ref(buffer_view);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_view_assert,  //
+                   iree_hal_inline_module_state_t,             //
+                   rriiCID, v) {
+  iree_host_size_t expected_shape_rank = 0;
+  iree_hal_dim_t* expected_shape_dims = NULL;
+  // TODO(benvanik): avoid the cast/alloca if not required.
+  IREE_VM_ABI_VLA_STACK_CAST(args, a4_count, a4, iree_hal_dim_t, 128,
+                             &expected_shape_rank, &expected_shape_dims);
+  return iree_hal_modules_buffer_view_assert(
+      args->r0, args->r1, (iree_hal_element_type_t)args->i2,
+      (iree_hal_encoding_type_t)args->i3, expected_shape_rank,
+      expected_shape_dims);
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_view_buffer,  //
+                   iree_hal_inline_module_state_t,             //
+                   r, r) {
+  iree_hal_buffer_view_t* buffer_view = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_hal_buffer_view_check_deref(args->r0, &buffer_view));
+  rets->r0 =
+      iree_hal_buffer_retain_ref(iree_hal_buffer_view_buffer(buffer_view));
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_view_element_type,  //
+                   iree_hal_inline_module_state_t,                   //
+                   r, i) {
+  iree_hal_buffer_view_t* buffer_view = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_hal_buffer_view_check_deref(args->r0, &buffer_view));
+  rets->i0 = (uint32_t)iree_hal_buffer_view_element_type(buffer_view);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_view_encoding_type,  //
+                   iree_hal_inline_module_state_t,                    //
+                   r, i) {
+  iree_hal_buffer_view_t* buffer_view = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_hal_buffer_view_check_deref(args->r0, &buffer_view));
+  rets->i0 = (uint32_t)iree_hal_buffer_view_encoding_type(buffer_view);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_view_rank,  //
+                   iree_hal_inline_module_state_t,           //
+                   r, i) {
+  iree_hal_buffer_view_t* buffer_view = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_hal_buffer_view_check_deref(args->r0, &buffer_view));
+  rets->i0 = (iree_vm_size_t)iree_hal_buffer_view_shape_rank(buffer_view);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_view_dim,  //
+                   iree_hal_inline_module_state_t,          //
+                   ri, I) {
+  iree_hal_buffer_view_t* buffer_view = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_hal_buffer_view_check_deref(args->r0, &buffer_view));
+  iree_vm_size_t index = (iree_vm_size_t)args->i1;
+  rets->i0 = (int64_t)iree_hal_buffer_view_shape_dim(buffer_view, index);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_buffer_view_trace,  //
+                   iree_hal_inline_module_state_t,            //
+                   rCrD, v) {
+  return iree_hal_modules_buffer_view_trace(args->r0, args->a1_count, args->a1,
+                                            state->host_allocator);
+}
+
+//===----------------------------------------------------------------------===//
+// iree_hal_device_t
+//===----------------------------------------------------------------------===//
+
+IREE_VM_ABI_EXPORT(iree_hal_inline_module_device_query_i64,  //
+                   iree_hal_inline_module_state_t,           //
+                   rr, iI) {
+  iree_vm_buffer_t* category = NULL;
+  IREE_RETURN_IF_ERROR(iree_vm_buffer_check_deref(args->r0, &category));
+  iree_string_view_t category_str = iree_vm_buffer_as_string(category);
+  iree_vm_buffer_t* key = NULL;
+  IREE_RETURN_IF_ERROR(iree_vm_buffer_check_deref(args->r1, &key));
+  iree_string_view_t key_str = iree_vm_buffer_as_string(key);
+
+  // TODO(benvanik): allow injection of a query function on the module. This
+  // would let us extend the queryable configuration with either synthetic
+  // properties or user-provided ones. For now we could at least provide
+  // compile-time configuration (like hosting architecture) but nothing dynamic
+  // (like cache sizes).
+  // The full HAL asks iree_hal_device_t but we don't have that here:
+  //   iree_hal_device_query_i64(device, category_str, key_str, &value);
+  (void)category_str;
+  (void)key_str;
+
+  int64_t value = 0;
+  iree_status_t query_status = iree_status_from_code(IREE_STATUS_NOT_FOUND);
+  rets->i0 = iree_status_consume_code(query_status) == IREE_STATUS_OK ? 1 : 0;
+  rets->i1 = value;
+  return iree_ok_status();
+}
+
+//===----------------------------------------------------------------------===//
+// VM module interface implementation
+//===----------------------------------------------------------------------===//
+
+// NOTE: this must match the ordering of the iree_hal_inline_module_exports_
+// table.
+static const iree_vm_native_function_ptr_t iree_hal_inline_module_funcs_[] = {
+#define EXPORT_FN(name, target_fn, arg_types, ret_types)       \
+  {                                                            \
+      .shim = (iree_vm_native_function_shim_t)                 \
+          iree_vm_shim_##arg_types##_##ret_types,              \
+      .target = (iree_vm_native_function_target_t)(target_fn), \
+  },
+#include "iree/modules/hal/inline/exports.inl"  // IWYU pragma: keep
+#undef EXPORT_FN
+};
+
+// NOTE: 0 length, but can't express that in C.
+static const iree_vm_native_import_descriptor_t
+    iree_hal_inline_module_imports_[1];
+
+static const iree_vm_native_export_descriptor_t
+    iree_hal_inline_module_exports_[] = {
+#define EXPORT_FN(name, target_fn, arg_types, ret_types)           \
+  {                                                                \
+      .local_name = iree_string_view_literal(name),                \
+      .calling_convention =                                        \
+          iree_string_view_literal("0" #arg_types "_" #ret_types), \
+      .attr_count = 0,                                             \
+      .attrs = NULL,                                               \
+  },
+#include "iree/modules/hal/inline/exports.inl"  // IWYU pragma: keep
+#undef EXPORT_FN
+};
+static_assert(IREE_ARRAYSIZE(iree_hal_inline_module_funcs_) ==
+                  IREE_ARRAYSIZE(iree_hal_inline_module_exports_),
+              "function pointer table must be 1:1 with exports");
+
+static const iree_vm_native_module_descriptor_t
+    iree_hal_inline_module_descriptor_ = {
+        .name = iree_string_view_literal("hal_inline"),
+        .version = IREE_HAL_INLINE_MODULE_VERSION_LATEST,
+        .attr_count = 0,
+        .attrs = NULL,
+        .dependency_count = 0,
+        .dependencies = NULL,
+        .import_count = 0,  // workaround for 0-length C struct
+        .imports = iree_hal_inline_module_imports_,
+        .export_count = IREE_ARRAYSIZE(iree_hal_inline_module_exports_),
+        .exports = iree_hal_inline_module_exports_,
+        .function_count = IREE_ARRAYSIZE(iree_hal_inline_module_funcs_),
+        .functions = iree_hal_inline_module_funcs_,
+};
+
+IREE_API_EXPORT iree_status_t iree_hal_inline_module_create(
+    iree_vm_instance_t* instance, iree_hal_inline_module_flags_t flags,
+    iree_hal_allocator_t* device_allocator, iree_allocator_t host_allocator,
+    iree_vm_module_t** out_module) {
+  IREE_ASSERT_ARGUMENT(instance);
+  IREE_ASSERT_ARGUMENT(device_allocator);
+  IREE_ASSERT_ARGUMENT(out_module);
+  *out_module = NULL;
+
+  // Setup the interface with the functions we implement ourselves. Any function
+  // we omit will be handled by the base native module.
+  static const iree_vm_module_t interface = {
+      .destroy = iree_hal_inline_module_destroy,
+      .alloc_state = iree_hal_inline_module_alloc_state,
+      .free_state = iree_hal_inline_module_free_state,
+      .notify = iree_hal_inline_module_notify,
+  };
+
+  // Allocate shared module state.
+  iree_host_size_t total_size =
+      iree_vm_native_module_size() + sizeof(iree_hal_inline_module_t);
+  iree_vm_module_t* base_module = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_allocator_malloc(host_allocator, total_size, (void**)&base_module));
+  memset(base_module, 0, total_size);
+  iree_status_t status = iree_vm_native_module_initialize(
+      &interface, &iree_hal_inline_module_descriptor_, instance, host_allocator,
+      base_module);
+  if (!iree_status_is_ok(status)) {
+    iree_allocator_free(host_allocator, base_module);
+    return status;
+  }
+
+  iree_hal_inline_module_t* module = IREE_HAL_INLINE_MODULE_CAST(base_module);
+  module->host_allocator = host_allocator;
+  module->device_allocator = device_allocator;
+  iree_hal_allocator_retain(module->device_allocator);
+  module->flags = flags;
+
+  *out_module = base_module;
+  return iree_ok_status();
+}
diff --git a/runtime/src/iree/modules/hal/inline/module.h b/runtime/src/iree/modules/hal/inline/module.h
new file mode 100644
index 0000000..f8e881d
--- /dev/null
+++ b/runtime/src/iree/modules/hal/inline/module.h
@@ -0,0 +1,39 @@
+// 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_MODULES_HAL_INLINE_MODULE_H_
+#define IREE_MODULES_HAL_INLINE_MODULE_H_
+
+#include <stdint.h>
+
+#include "iree/base/api.h"
+#include "iree/hal/api.h"
+#include "iree/modules/hal/types.h"
+#include "iree/vm/api.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+enum iree_hal_inline_module_flag_bits_t {
+  IREE_HAL_INLINE_MODULE_FLAG_NONE = 0u,
+};
+typedef uint32_t iree_hal_inline_module_flags_t;
+
+// Creates the inline HAL module for local execution.
+// This provides ABI compatibility with the full HAL implementation in a much
+// smaller footprint. The given |device_allocator| will be used for buffer
+// allocations.
+IREE_API_EXPORT iree_status_t iree_hal_inline_module_create(
+    iree_vm_instance_t* instance, iree_hal_inline_module_flags_t flags,
+    iree_hal_allocator_t* device_allocator, iree_allocator_t host_allocator,
+    iree_vm_module_t** out_module);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif  // IREE_MODULES_HAL_INLINE_MODULE_H_
diff --git a/runtime/src/iree/modules/vmvx/BUILD b/runtime/src/iree/modules/vmvx/BUILD
index aeef1e5..05ba13a 100644
--- a/runtime/src/iree/modules/vmvx/BUILD
+++ b/runtime/src/iree/modules/vmvx/BUILD
@@ -20,6 +20,9 @@
     hdrs = [
         "module.h",
     ],
+    defines = [
+        "IREE_HAVE_VMVX_MODULE",
+    ],
     textual_hdrs = [
         "exports.inl",
     ],
diff --git a/runtime/src/iree/modules/vmvx/CMakeLists.txt b/runtime/src/iree/modules/vmvx/CMakeLists.txt
index 3f49441..97d5239 100644
--- a/runtime/src/iree/modules/vmvx/CMakeLists.txt
+++ b/runtime/src/iree/modules/vmvx/CMakeLists.txt
@@ -18,6 +18,8 @@
     "exports.inl"
   SRCS
     "module.c"
+  DEFINES
+    "IREE_HAVE_VMVX_MODULE"
   DEPS
     iree::base
     iree::base::tracing
diff --git a/runtime/src/iree/tooling/BUILD b/runtime/src/iree/tooling/BUILD
index 4f97fa5..605c1fb 100644
--- a/runtime/src/iree/tooling/BUILD
+++ b/runtime/src/iree/tooling/BUILD
@@ -24,6 +24,7 @@
         "//runtime/src/iree/base/internal:flags",
         "//runtime/src/iree/hal",
         "//runtime/src/iree/modules/hal",
+        "//runtime/src/iree/modules/hal/inline",
         "//runtime/src/iree/vm",
         "//runtime/src/iree/vm:bytecode_module",
     ],
diff --git a/runtime/src/iree/tooling/CMakeLists.txt b/runtime/src/iree/tooling/CMakeLists.txt
index e829f1e..f36aee9 100644
--- a/runtime/src/iree/tooling/CMakeLists.txt
+++ b/runtime/src/iree/tooling/CMakeLists.txt
@@ -25,6 +25,7 @@
     iree::base::tracing
     iree::hal
     iree::modules::hal
+    iree::modules::hal::inline
     iree::vm
     iree::vm::bytecode_module
   PUBLIC
@@ -166,3 +167,11 @@
 endif()
 
 ### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
+
+# We're co-opting the VMVX module loader option for this as the inline-static
+# model is essentially just an inlined loader.
+# These tooling targets are intended for iree-* tools and not end-user binaries
+# where binary size or dependency constraints matter.
+if(IREE_HAL_EXECUTABLE_LOADER_VMVX_MODULE)
+  target_link_libraries(iree_tooling_context_util INTERFACE iree_modules_vmvx_vmvx)
+endif()
diff --git a/runtime/src/iree/tooling/context_util.c b/runtime/src/iree/tooling/context_util.c
index 8883bde..85e4d7d 100644
--- a/runtime/src/iree/tooling/context_util.c
+++ b/runtime/src/iree/tooling/context_util.c
@@ -13,10 +13,15 @@
 #include "iree/base/internal/file_io.h"
 #include "iree/base/internal/flags.h"
 #include "iree/base/tracing.h"
+#include "iree/modules/hal/inline/module.h"
 #include "iree/modules/hal/module.h"
 #include "iree/tooling/device_util.h"
 #include "iree/vm/bytecode_module.h"
 
+#if defined(IREE_HAVE_VMVX_MODULE)
+#include "iree/modules/vmvx/module.h"
+#endif  // IREE_HAVE_VMVX_MODULE
+
 //===----------------------------------------------------------------------===//
 // Module loading
 //===----------------------------------------------------------------------===//
@@ -142,6 +147,50 @@
   return status;
 }
 
+static iree_status_t iree_tooling_load_hal_inline_module(
+    iree_vm_instance_t* instance, iree_allocator_t host_allocator,
+    iree_vm_module_t** out_module,
+    iree_hal_allocator_t** out_device_allocator) {
+  IREE_ASSERT_ARGUMENT(instance);
+  IREE_ASSERT_ARGUMENT(out_module);
+  IREE_ASSERT_ARGUMENT(out_device_allocator);
+  if (*out_device_allocator) {
+    return iree_make_status(IREE_STATUS_FAILED_PRECONDITION,
+                            "inline HAL module cannot be used with other "
+                            "primary HAL module types");
+  }
+  *out_module = NULL;
+  *out_device_allocator = NULL;
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  // Register required types before creating the module.
+  IREE_RETURN_AND_END_ZONE_IF_ERROR(
+      z0, iree_hal_module_register_inline_types(instance));
+
+  // Create default heap device allocator.
+  iree_hal_allocator_t* device_allocator = NULL;
+  IREE_RETURN_AND_END_ZONE_IF_ERROR(
+      z0, iree_tooling_create_inline_device_allocator_from_flags(
+              host_allocator, &device_allocator));
+
+  // Create the module; it's immutable and can be reused but we don't do that in
+  // this tooling.
+  iree_hal_inline_module_flags_t flags = IREE_HAL_INLINE_MODULE_FLAG_NONE;
+  iree_vm_module_t* module = NULL;
+  iree_status_t status = iree_hal_inline_module_create(
+      instance, flags, device_allocator, host_allocator, &module);
+
+  if (iree_status_is_ok(status)) {
+    *out_module = module;
+    *out_device_allocator = device_allocator;
+  } else {
+    iree_hal_allocator_release(device_allocator);
+    iree_vm_module_release(module);
+  }
+  IREE_TRACE_ZONE_END(z0);
+  return status;
+}
+
 //===----------------------------------------------------------------------===//
 // Module management
 //===----------------------------------------------------------------------===//
@@ -222,6 +271,13 @@
     IREE_RETURN_IF_ERROR(iree_tooling_load_hal_async_module(
         state->instance, state->default_device_uri, state->host_allocator,
         &module, &state->device, &state->device_allocator));
+  } else if (iree_string_view_equal(dependency->name, IREE_SV("hal_inline"))) {
+    IREE_RETURN_IF_ERROR(iree_tooling_load_hal_inline_module(
+        state->instance, state->host_allocator, &module,
+        &state->device_allocator));
+  } else if (iree_string_view_equal(dependency->name, IREE_SV("vmvx"))) {
+    IREE_RETURN_IF_ERROR(iree_vmvx_module_create(
+        state->instance, state->host_allocator, &module));
   } else if (iree_all_bits_set(dependency->flags,
                                IREE_VM_MODULE_DEPENDENCY_FLAG_REQUIRED)) {
     // Required but not found; fail.
diff --git a/runtime/src/iree/vm/shims.c b/runtime/src/iree/vm/shims.c
index 9605efa..57707d7 100644
--- a/runtime/src/iree/vm/shims.c
+++ b/runtime/src/iree/vm/shims.c
@@ -14,6 +14,7 @@
 IREE_VM_ABI_DEFINE_SHIM(r, iii);
 IREE_VM_ABI_DEFINE_SHIM(r, iiii);
 IREE_VM_ABI_DEFINE_SHIM(r, r);
+IREE_VM_ABI_DEFINE_SHIM(r, rI);
 IREE_VM_ABI_DEFINE_SHIM(r, v);
 IREE_VM_ABI_DEFINE_SHIM(rCiD, i);
 IREE_VM_ABI_DEFINE_SHIM(rCrD, v);
@@ -45,6 +46,7 @@
 IREE_VM_ABI_DEFINE_SHIM(rr, r);
 IREE_VM_ABI_DEFINE_SHIM(rr, v);
 IREE_VM_ABI_DEFINE_SHIM(rr, ii);
+IREE_VM_ABI_DEFINE_SHIM(rr, iI);
 IREE_VM_ABI_DEFINE_SHIM(rrr, iI);
 IREE_VM_ABI_DEFINE_SHIM(rrCirIID, r);
 IREE_VM_ABI_DEFINE_SHIM(rriCiD, v);
@@ -55,6 +57,7 @@
 IREE_VM_ABI_DEFINE_SHIM(rrirCID, v);
 IREE_VM_ABI_DEFINE_SHIM(rrirI, v);
 IREE_VM_ABI_DEFINE_SHIM(rrIrII, v);
+IREE_VM_ABI_DEFINE_SHIM(rrIii, v);
 IREE_VM_ABI_DEFINE_SHIM(rrrIii, v);
 IREE_VM_ABI_DEFINE_SHIM(rIrriiiI, r);
 IREE_VM_ABI_DEFINE_SHIM(rIrrr, v);
@@ -62,6 +65,8 @@
 IREE_VM_ABI_DEFINE_SHIM(CrID, r);
 IREE_VM_ABI_DEFINE_SHIM(CrD, r);
 IREE_VM_ABI_DEFINE_SHIM(iCrD, i);
+IREE_VM_ABI_DEFINE_SHIM(iI, rr);
+IREE_VM_ABI_DEFINE_SHIM(irII, rr);
 IREE_VM_ABI_DEFINE_SHIM(v, i);
 IREE_VM_ABI_DEFINE_SHIM(v, r);
 IREE_VM_ABI_DEFINE_SHIM(v, v);
diff --git a/runtime/src/iree/vm/shims.h b/runtime/src/iree/vm/shims.h
index 6e27ff9..f7f3f38 100644
--- a/runtime/src/iree/vm/shims.h
+++ b/runtime/src/iree/vm/shims.h
@@ -346,6 +346,14 @@
   int64_t i5;
 });
 
+IREE_VM_ABI_FIXED_STRUCT(rrIii, {
+  iree_vm_ref_t r0;
+  iree_vm_ref_t r1;
+  int64_t i2;
+  int32_t i3;
+  int32_t i4;
+});
+
 IREE_VM_ABI_FIXED_STRUCT(rrrIii, {
   iree_vm_ref_t r0;
   iree_vm_ref_t r1;
@@ -522,6 +530,7 @@
 IREE_VM_ABI_DECLARE_SHIM(r, iii);
 IREE_VM_ABI_DECLARE_SHIM(r, iiii);
 IREE_VM_ABI_DECLARE_SHIM(r, r);
+IREE_VM_ABI_DECLARE_SHIM(r, rI);
 IREE_VM_ABI_DECLARE_SHIM(r, v);
 IREE_VM_ABI_DECLARE_SHIM(rCiD, i);
 IREE_VM_ABI_DECLARE_SHIM(rCrD, v);
@@ -553,6 +562,7 @@
 IREE_VM_ABI_DECLARE_SHIM(rr, r);
 IREE_VM_ABI_DECLARE_SHIM(rr, v);
 IREE_VM_ABI_DECLARE_SHIM(rr, ii);
+IREE_VM_ABI_DECLARE_SHIM(rr, iI);
 IREE_VM_ABI_DECLARE_SHIM(rrr, iI);
 IREE_VM_ABI_DECLARE_SHIM(rrCirIID, r);
 IREE_VM_ABI_DECLARE_SHIM(rriCiD, v);
@@ -563,6 +573,7 @@
 IREE_VM_ABI_DECLARE_SHIM(rrirCID, v);
 IREE_VM_ABI_DECLARE_SHIM(rrirI, v);
 IREE_VM_ABI_DECLARE_SHIM(rrIrII, v);
+IREE_VM_ABI_DECLARE_SHIM(rrIii, v);
 IREE_VM_ABI_DECLARE_SHIM(rrrIii, v);
 IREE_VM_ABI_DECLARE_SHIM(rIrriiiI, r);
 IREE_VM_ABI_DECLARE_SHIM(rIrrr, v);
@@ -570,6 +581,8 @@
 IREE_VM_ABI_DECLARE_SHIM(CrID, r);
 IREE_VM_ABI_DECLARE_SHIM(CrD, r);
 IREE_VM_ABI_DECLARE_SHIM(iCrD, i);
+IREE_VM_ABI_DECLARE_SHIM(iI, rr);
+IREE_VM_ABI_DECLARE_SHIM(irII, rr);
 IREE_VM_ABI_DECLARE_SHIM(v, i);
 IREE_VM_ABI_DECLARE_SHIM(v, r);
 IREE_VM_ABI_DECLARE_SHIM(v, v);