Merge branch 'google' into main-to-google
diff --git a/.gitmodules b/.gitmodules
index a00f81a..2d8c87a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -47,3 +47,6 @@
 [submodule "third_party/libyaml"]
 	path = third_party/libyaml
 	url = https://github.com/yaml/libyaml.git
+[submodule "third_party/webgpu-headers"]
+	path = third_party/webgpu-headers
+	url = https://github.com/webgpu-native/webgpu-headers.git
diff --git a/SUBMODULE_VERSIONS.txt b/SUBMODULE_VERSIONS.txt
index 5e2cfcd..5d408df 100644
--- a/SUBMODULE_VERSIONS.txt
+++ b/SUBMODULE_VERSIONS.txt
@@ -11,6 +11,7 @@
 f5417a4b6633c3217c9a1bc2f0c70b1454975ba7 third_party/spirv_headers
 b42009b3b9d4ca35bc703f5310eedc74f584be58 third_party/stblib
 951452b6150ee86e2562aaf46a4bcceb4aa99697 third_party/tensorflow
-f03b677ffa0fd96fcf859c32e79b740fac7dd59e third_party/tracy
+50f7deb1a389bd3785c12fbe0be74128343f11f7 third_party/tracy
 9d10a96f2d57c3c37e167f2e73c9a31ac2e51fa5 third_party/vulkan_headers
 8d4a9e9174a9c6ad6a3a3ae981b915ef13fc12c4 third_party/vulkan_memory_allocator
+c8e0b39f6f6f1edded5c4adf7d46aa4d2a95befe third_party/webgpu-headers
diff --git a/experimental/rocm/CMakeLists.txt b/experimental/rocm/CMakeLists.txt
index db57bd3..185bd5c 100644
--- a/experimental/rocm/CMakeLists.txt
+++ b/experimental/rocm/CMakeLists.txt
@@ -51,8 +51,6 @@
     iree::base::internal
     iree::base::internal::flatcc
     iree::base::internal::synchronization
-    iree::base::logging
-    iree::base::status
     iree::base::tracing
     iree::hal
     iree::schemas::rocm_executable_def_c_fbs
@@ -88,6 +86,7 @@
     "dynamic_symbols_test.cc"
   DEPS
     ::dynamic_symbols
+    iree::base
     iree::testing::gtest
     iree::testing::gtest_main
   LABELS
diff --git a/experimental/rocm/api.h b/experimental/rocm/api.h
index 638fedd..68fa191 100644
--- a/experimental/rocm/api.h
+++ b/experimental/rocm/api.h
@@ -21,7 +21,7 @@
 //===----------------------------------------------------------------------===//
 
 // ROCM driver creation options.
-typedef struct {
+typedef struct iree_hal_rocm_driver_options_t {
   // Index of the default ROCM device to use within the list of available
   // devices.
   int default_device_index;
diff --git a/experimental/rocm/context_wrapper.h b/experimental/rocm/context_wrapper.h
index 514207d..2c51424 100644
--- a/experimental/rocm/context_wrapper.h
+++ b/experimental/rocm/context_wrapper.h
@@ -13,7 +13,7 @@
 
 // Structure to wrap all objects constant within a context. This makes it
 // simpler to pass it to the different objects and saves memory.
-typedef struct {
+typedef struct iree_hal_rocm_context_wrapper_t {
   hipCtx_t rocm_context;
   iree_allocator_t host_allocator;
   iree_hal_rocm_dynamic_symbols_t *syms;
diff --git a/experimental/rocm/descriptor_set_layout.c b/experimental/rocm/descriptor_set_layout.c
index 0ca1cc8..69fb4af 100644
--- a/experimental/rocm/descriptor_set_layout.c
+++ b/experimental/rocm/descriptor_set_layout.c
@@ -6,10 +6,12 @@
 
 #include "experimental/rocm/descriptor_set_layout.h"
 
-#include "experimental/rocm/status_util.h"
+#include <stddef.h>
+
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
-typedef struct {
+typedef struct iree_hal_rocm_descriptor_set_layout_t {
   iree_hal_resource_t resource;
   iree_hal_rocm_context_wrapper_t *context;
 } iree_hal_rocm_descriptor_set_layout_t;
diff --git a/experimental/rocm/descriptor_set_layout.h b/experimental/rocm/descriptor_set_layout.h
index 933c9c6..fb07b76 100644
--- a/experimental/rocm/descriptor_set_layout.h
+++ b/experimental/rocm/descriptor_set_layout.h
@@ -8,6 +8,7 @@
 #define IREE_HAL_ROCM_DESCRIPTOR_SET_LAYOUT_H_
 
 #include "experimental/rocm/context_wrapper.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/direct_command_buffer.c b/experimental/rocm/direct_command_buffer.c
index 9d56001..3d8706a 100644
--- a/experimental/rocm/direct_command_buffer.c
+++ b/experimental/rocm/direct_command_buffer.c
@@ -6,10 +6,15 @@
 
 #include "experimental/rocm/direct_command_buffer.h"
 
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "experimental/rocm/dynamic_symbols.h"
 #include "experimental/rocm/native_executable.h"
 #include "experimental/rocm/rocm_buffer.h"
-#include "experimental/rocm/rocm_event.h"
 #include "experimental/rocm/status_util.h"
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
 // Command buffer implementation that directly maps to rocm direct.
@@ -18,13 +23,13 @@
 
 typedef struct {
   iree_hal_resource_t resource;
-  iree_hal_rocm_context_wrapper_t *context;
+  iree_hal_rocm_context_wrapper_t* context;
   iree_hal_command_buffer_mode_t mode;
   iree_hal_command_category_t allowed_categories;
   iree_hal_queue_affinity_t queue_affinity;
   size_t total_size;
   // Keep track of the current set of kernel arguments.
-  void *current_descriptor[];
+  void* current_descriptor[];
 } iree_hal_rocm_direct_command_buffer_t;
 
 #define IREE_HAL_ROCM_MAX_BINDING_COUNT 64
@@ -32,29 +37,29 @@
 extern const iree_hal_command_buffer_vtable_t
     iree_hal_rocm_direct_command_buffer_vtable;
 
-static iree_hal_rocm_direct_command_buffer_t *
+static iree_hal_rocm_direct_command_buffer_t*
 iree_hal_rocm_direct_command_buffer_cast(
-    iree_hal_command_buffer_t *base_value) {
+    iree_hal_command_buffer_t* base_value) {
   IREE_HAL_ASSERT_TYPE(base_value, &iree_hal_rocm_direct_command_buffer_vtable);
-  return (iree_hal_rocm_direct_command_buffer_t *)base_value;
+  return (iree_hal_rocm_direct_command_buffer_t*)base_value;
 }
 
 iree_status_t iree_hal_rocm_direct_command_buffer_allocate(
-    iree_hal_rocm_context_wrapper_t *context,
+    iree_hal_rocm_context_wrapper_t* context,
     iree_hal_command_buffer_mode_t mode,
     iree_hal_command_category_t command_categories,
     iree_hal_queue_affinity_t queue_affinity,
-    iree_hal_command_buffer_t **out_command_buffer) {
+    iree_hal_command_buffer_t** out_command_buffer) {
   IREE_ASSERT_ARGUMENT(context);
   IREE_ASSERT_ARGUMENT(out_command_buffer);
   IREE_TRACE_ZONE_BEGIN(z0);
 
-  iree_hal_rocm_direct_command_buffer_t *command_buffer = NULL;
+  iree_hal_rocm_direct_command_buffer_t* command_buffer = NULL;
   size_t total_size = sizeof(*command_buffer) +
-                      IREE_HAL_ROCM_MAX_BINDING_COUNT * sizeof(void *) +
+                      IREE_HAL_ROCM_MAX_BINDING_COUNT * sizeof(void*) +
                       IREE_HAL_ROCM_MAX_BINDING_COUNT * sizeof(hipDeviceptr_t);
   iree_status_t status = iree_allocator_malloc(
-      context->host_allocator, total_size, (void **)&command_buffer);
+      context->host_allocator, total_size, (void**)&command_buffer);
   if (iree_status_is_ok(status)) {
     iree_hal_resource_initialize(&iree_hal_rocm_direct_command_buffer_vtable,
                                  &command_buffer->resource);
@@ -62,15 +67,15 @@
     command_buffer->mode = mode;
     command_buffer->allowed_categories = command_categories;
     command_buffer->queue_affinity = queue_affinity;
-    hipDeviceptr_t *device_ptrs =
-        (hipDeviceptr_t *)(command_buffer->current_descriptor +
-                           IREE_HAL_ROCM_MAX_BINDING_COUNT);
+    hipDeviceptr_t* device_ptrs =
+        (hipDeviceptr_t*)(command_buffer->current_descriptor +
+                          IREE_HAL_ROCM_MAX_BINDING_COUNT);
     for (size_t i = 0; i < IREE_HAL_ROCM_MAX_BINDING_COUNT; i++) {
       command_buffer->current_descriptor[i] = &device_ptrs[i];
     }
     command_buffer->total_size = total_size;
 
-    *out_command_buffer = (iree_hal_command_buffer_t *)command_buffer;
+    *out_command_buffer = (iree_hal_command_buffer_t*)command_buffer;
   }
 
   IREE_TRACE_ZONE_END(z0);
@@ -78,8 +83,8 @@
 }
 
 static void iree_hal_rocm_direct_command_buffer_destroy(
-    iree_hal_command_buffer_t *base_command_buffer) {
-  iree_hal_rocm_direct_command_buffer_t *command_buffer =
+    iree_hal_command_buffer_t* base_command_buffer) {
+  iree_hal_rocm_direct_command_buffer_t* command_buffer =
       iree_hal_rocm_direct_command_buffer_cast(base_command_buffer);
   IREE_TRACE_ZONE_BEGIN(z0);
 
@@ -89,91 +94,103 @@
 }
 
 static iree_hal_command_buffer_mode_t iree_hal_rocm_direct_command_buffer_mode(
-    const iree_hal_command_buffer_t *base_command_buffer) {
-  const iree_hal_rocm_direct_command_buffer_t *command_buffer =
-      (const iree_hal_rocm_direct_command_buffer_t *)(base_command_buffer);
+    const iree_hal_command_buffer_t* base_command_buffer) {
+  const iree_hal_rocm_direct_command_buffer_t* command_buffer =
+      (const iree_hal_rocm_direct_command_buffer_t*)(base_command_buffer);
   return command_buffer->mode;
 }
 
 static iree_hal_command_category_t
 iree_hal_rocm_direct_command_buffer_allowed_categories(
-    const iree_hal_command_buffer_t *base_command_buffer) {
-  const iree_hal_rocm_direct_command_buffer_t *command_buffer =
-      (const iree_hal_rocm_direct_command_buffer_t *)(base_command_buffer);
+    const iree_hal_command_buffer_t* base_command_buffer) {
+  const iree_hal_rocm_direct_command_buffer_t* command_buffer =
+      (const iree_hal_rocm_direct_command_buffer_t*)(base_command_buffer);
   return command_buffer->allowed_categories;
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_begin(
-    iree_hal_command_buffer_t *base_command_buffer) {
+    iree_hal_command_buffer_t* base_command_buffer) {
   return iree_ok_status();
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_end(
-    iree_hal_command_buffer_t *base_command_buffer) {
+    iree_hal_command_buffer_t* base_command_buffer) {
   return iree_ok_status();
 }
 
+static void iree_hal_rocm_direct_command_buffer_begin_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer, iree_string_view_t label,
+    iree_hal_label_color_t label_color,
+    const iree_hal_label_location_t* location) {
+  // TODO(benvanik): tracy event stack.
+}
+
+static void iree_hal_rocm_direct_command_buffer_end_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer) {
+  // TODO(benvanik): tracy event stack.
+}
+
 static iree_status_t iree_hal_rocm_direct_command_buffer_execution_barrier(
-    iree_hal_command_buffer_t *base_command_buffer,
+    iree_hal_command_buffer_t* base_command_buffer,
     iree_hal_execution_stage_t source_stage_mask,
     iree_hal_execution_stage_t target_stage_mask,
     iree_hal_execution_barrier_flags_t flags,
     iree_host_size_t memory_barrier_count,
-    const iree_hal_memory_barrier_t *memory_barriers,
+    const iree_hal_memory_barrier_t* memory_barriers,
     iree_host_size_t buffer_barrier_count,
-    const iree_hal_buffer_barrier_t *buffer_barriers) {
+    const iree_hal_buffer_barrier_t* buffer_barriers) {
   // TODO: Implement barrier
   return iree_ok_status();
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_signal_event(
-    iree_hal_command_buffer_t *base_command_buffer, iree_hal_event_t *event,
+    iree_hal_command_buffer_t* base_command_buffer, iree_hal_event_t* event,
     iree_hal_execution_stage_t source_stage_mask) {
   // TODO: Implement barrier
   return iree_ok_status();
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_reset_event(
-    iree_hal_command_buffer_t *base_command_buffer, iree_hal_event_t *event,
+    iree_hal_command_buffer_t* base_command_buffer, iree_hal_event_t* event,
     iree_hal_execution_stage_t source_stage_mask) {
   // TODO: Implement barrier
   return iree_ok_status();
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_wait_events(
-    iree_hal_command_buffer_t *base_command_buffer,
-    iree_host_size_t event_count, const iree_hal_event_t **events,
+    iree_hal_command_buffer_t* base_command_buffer,
+    iree_host_size_t event_count, const iree_hal_event_t** events,
     iree_hal_execution_stage_t source_stage_mask,
     iree_hal_execution_stage_t target_stage_mask,
     iree_host_size_t memory_barrier_count,
-    const iree_hal_memory_barrier_t *memory_barriers,
+    const iree_hal_memory_barrier_t* memory_barriers,
     iree_host_size_t buffer_barrier_count,
-    const iree_hal_buffer_barrier_t *buffer_barriers) {
+    const iree_hal_buffer_barrier_t* buffer_barriers) {
   // TODO: Implement barrier
   return iree_ok_status();
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_discard_buffer(
-    iree_hal_command_buffer_t *base_command_buffer, iree_hal_buffer_t *buffer) {
+    iree_hal_command_buffer_t* base_command_buffer, iree_hal_buffer_t* buffer) {
   // nothing to do.
   return iree_ok_status();
 }
 
 // Splats a pattern value of 1, 2, or 4 bytes out to a 4 byte value.
-static uint32_t iree_hal_rocm_splat_pattern(const void *pattern,
+static uint32_t iree_hal_rocm_splat_pattern(const void* pattern,
                                             size_t pattern_length) {
   switch (pattern_length) {
     case 1: {
-      uint32_t pattern_value = *(const uint8_t *)(pattern);
+      uint32_t pattern_value = *(const uint8_t*)(pattern);
       return (pattern_value << 24) | (pattern_value << 16) |
              (pattern_value << 8) | pattern_value;
     }
     case 2: {
-      uint32_t pattern_value = *(const uint16_t *)(pattern);
+      uint32_t pattern_value = *(const uint16_t*)(pattern);
       return (pattern_value << 16) | pattern_value;
     }
     case 4: {
-      uint32_t pattern_value = *(const uint32_t *)(pattern);
+      uint32_t pattern_value = *(const uint32_t*)(pattern);
       return pattern_value;
     }
     default:
@@ -182,11 +199,11 @@
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_fill_buffer(
-    iree_hal_command_buffer_t *base_command_buffer,
-    iree_hal_buffer_t *target_buffer, iree_device_size_t target_offset,
-    iree_device_size_t length, const void *pattern,
+    iree_hal_command_buffer_t* base_command_buffer,
+    iree_hal_buffer_t* target_buffer, iree_device_size_t target_offset,
+    iree_device_size_t length, const void* pattern,
     iree_host_size_t pattern_length) {
-  iree_hal_rocm_direct_command_buffer_t *command_buffer =
+  iree_hal_rocm_direct_command_buffer_t* command_buffer =
       iree_hal_rocm_direct_command_buffer_cast(base_command_buffer);
 
   hipDeviceptr_t target_device_buffer = iree_hal_rocm_buffer_device_pointer(
@@ -205,19 +222,19 @@
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_update_buffer(
-    iree_hal_command_buffer_t *base_command_buffer, const void *source_buffer,
-    iree_host_size_t source_offset, iree_hal_buffer_t *target_buffer,
+    iree_hal_command_buffer_t* base_command_buffer, const void* source_buffer,
+    iree_host_size_t source_offset, iree_hal_buffer_t* target_buffer,
     iree_device_size_t target_offset, iree_device_size_t length) {
   return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                           "need rocm implementation");
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_copy_buffer(
-    iree_hal_command_buffer_t *base_command_buffer,
-    iree_hal_buffer_t *source_buffer, iree_device_size_t source_offset,
-    iree_hal_buffer_t *target_buffer, iree_device_size_t target_offset,
+    iree_hal_command_buffer_t* base_command_buffer,
+    iree_hal_buffer_t* source_buffer, iree_device_size_t source_offset,
+    iree_hal_buffer_t* target_buffer, iree_device_size_t target_offset,
     iree_device_size_t length) {
-  iree_hal_rocm_direct_command_buffer_t *command_buffer =
+  iree_hal_rocm_direct_command_buffer_t* command_buffer =
       iree_hal_rocm_direct_command_buffer_cast(base_command_buffer);
 
   hipDeviceptr_t target_device_buffer = iree_hal_rocm_buffer_device_pointer(
@@ -237,9 +254,9 @@
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_push_constants(
-    iree_hal_command_buffer_t *base_command_buffer,
-    iree_hal_executable_layout_t *executable_layout, iree_host_size_t offset,
-    const void *values, iree_host_size_t values_length) {
+    iree_hal_command_buffer_t* base_command_buffer,
+    iree_hal_executable_layout_t* executable_layout, iree_host_size_t offset,
+    const void* values, iree_host_size_t values_length) {
   return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                           "need rocm implementation");
 }
@@ -251,20 +268,20 @@
 } iree_hal_rocm_binding_mapping_t;
 
 // Helper to sort the binding based on their binding index.
-static int compare_binding_index(const void *a, const void *b) {
+static int compare_binding_index(const void* a, const void* b) {
   const iree_hal_rocm_binding_mapping_t buffer_a =
-      *(const iree_hal_rocm_binding_mapping_t *)a;
+      *(const iree_hal_rocm_binding_mapping_t*)a;
   const iree_hal_rocm_binding_mapping_t buffer_b =
-      *(const iree_hal_rocm_binding_mapping_t *)b;
+      *(const iree_hal_rocm_binding_mapping_t*)b;
   return buffer_a.binding < buffer_b.binding ? -1 : 1;
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_push_descriptor_set(
-    iree_hal_command_buffer_t *base_command_buffer,
-    iree_hal_executable_layout_t *executable_layout, uint32_t set,
+    iree_hal_command_buffer_t* base_command_buffer,
+    iree_hal_executable_layout_t* executable_layout, uint32_t set,
     iree_host_size_t binding_count,
-    const iree_hal_descriptor_set_binding_t *bindings) {
-  iree_hal_rocm_direct_command_buffer_t *command_buffer =
+    const iree_hal_descriptor_set_binding_t* bindings) {
+  iree_hal_rocm_direct_command_buffer_t* command_buffer =
       iree_hal_rocm_direct_command_buffer_cast(base_command_buffer);
   // Convention with the compiler side. We map bindings to kernel argument.
   // We compact the bindings to get a dense set of arguments and keep them order
@@ -286,26 +303,26 @@
         iree_hal_rocm_buffer_device_pointer(
             iree_hal_buffer_allocated_buffer(binding.buffer)) +
         iree_hal_buffer_byte_offset(binding.buffer) + binding.offset;
-    *((hipDeviceptr_t *)command_buffer->current_descriptor[i]) = device_ptr;
+    *((hipDeviceptr_t*)command_buffer->current_descriptor[i]) = device_ptr;
   }
   return iree_ok_status();
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_bind_descriptor_set(
-    iree_hal_command_buffer_t *base_command_buffer,
-    iree_hal_executable_layout_t *executable_layout, uint32_t set,
-    iree_hal_descriptor_set_t *descriptor_set,
+    iree_hal_command_buffer_t* base_command_buffer,
+    iree_hal_executable_layout_t* executable_layout, uint32_t set,
+    iree_hal_descriptor_set_t* descriptor_set,
     iree_host_size_t dynamic_offset_count,
-    const iree_device_size_t *dynamic_offsets) {
+    const iree_device_size_t* dynamic_offsets) {
   return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                           "need rocm implementation");
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_dispatch(
-    iree_hal_command_buffer_t *base_command_buffer,
-    iree_hal_executable_t *executable, int32_t entry_point,
+    iree_hal_command_buffer_t* base_command_buffer,
+    iree_hal_executable_t* executable, int32_t entry_point,
     uint32_t workgroup_x, uint32_t workgroup_y, uint32_t workgroup_z) {
-  iree_hal_rocm_direct_command_buffer_t *command_buffer =
+  iree_hal_rocm_direct_command_buffer_t* command_buffer =
       iree_hal_rocm_direct_command_buffer_cast(base_command_buffer);
   iree_hal_rocm_direct_command_buffer_cast(base_command_buffer);
 
@@ -327,9 +344,9 @@
 }
 
 static iree_status_t iree_hal_rocm_direct_command_buffer_dispatch_indirect(
-    iree_hal_command_buffer_t *base_command_buffer,
-    iree_hal_executable_t *executable, int32_t entry_point,
-    iree_hal_buffer_t *workgroups_buffer,
+    iree_hal_command_buffer_t* base_command_buffer,
+    iree_hal_executable_t* executable, int32_t entry_point,
+    iree_hal_buffer_t* workgroups_buffer,
     iree_device_size_t workgroups_offset) {
   return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                           "need rocm implementation");
@@ -343,6 +360,9 @@
             iree_hal_rocm_direct_command_buffer_allowed_categories,
         .begin = iree_hal_rocm_direct_command_buffer_begin,
         .end = iree_hal_rocm_direct_command_buffer_end,
+        .begin_debug_group =
+            iree_hal_rocm_direct_command_buffer_begin_debug_group,
+        .end_debug_group = iree_hal_rocm_direct_command_buffer_end_debug_group,
         .execution_barrier =
             iree_hal_rocm_direct_command_buffer_execution_barrier,
         .signal_event = iree_hal_rocm_direct_command_buffer_signal_event,
diff --git a/experimental/rocm/direct_command_buffer.h b/experimental/rocm/direct_command_buffer.h
index 70519c9..0145b15 100644
--- a/experimental/rocm/direct_command_buffer.h
+++ b/experimental/rocm/direct_command_buffer.h
@@ -10,6 +10,7 @@
 #include "experimental/rocm/context_wrapper.h"
 #include "experimental/rocm/dynamic_symbols.h"
 #include "experimental/rocm/rocm_headers.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/dynamic_symbols.c b/experimental/rocm/dynamic_symbols.c
index 0643cf1..d676838 100644
--- a/experimental/rocm/dynamic_symbols.c
+++ b/experimental/rocm/dynamic_symbols.c
@@ -6,7 +6,7 @@
 
 #include "experimental/rocm/dynamic_symbols.h"
 
-#include <stddef.h>
+#include <string.h>
 
 #include "iree/base/internal/dynamic_library.h"
 #include "iree/base/target_platform.h"
@@ -29,7 +29,7 @@
         syms->loader_library, kName, (void **)&syms->rocmSymbolName)); \
   }
 #define RC_PFN_STR_DECL(rocmSymbolName, ...) RC_PFN_DECL(rocmSymbolName, ...)
-#include "experimental/rocm/dynamic_symbol_tables.h"
+#include "experimental/rocm/dynamic_symbol_tables.h"  // IWYU pragma: keep
 #undef RC_PFN_DECL
 #undef RC_PFN_STR_DECL
   return iree_ok_status();
diff --git a/experimental/rocm/dynamic_symbols.h b/experimental/rocm/dynamic_symbols.h
index 2894626..00a58b0 100644
--- a/experimental/rocm/dynamic_symbols.h
+++ b/experimental/rocm/dynamic_symbols.h
@@ -19,14 +19,14 @@
 // loads all the function declared in `dynamic_symbol_tables.def` and fail if
 // any of the symbol is not available. The functions signatures are matching
 // the declarations in `hipruntime.h`.
-typedef struct {
+typedef struct iree_hal_rocm_dynamic_symbols_t {
   iree_dynamic_library_t *loader_library;
 
 #define RC_PFN_DECL(rocmSymbolName, ...) \
   hipError_t (*rocmSymbolName)(__VA_ARGS__);
 #define RC_PFN_STR_DECL(rocmSymbolName, ...) \
   const char *(*rocmSymbolName)(__VA_ARGS__);
-#include "experimental/rocm/dynamic_symbol_tables.h"
+#include "experimental/rocm/dynamic_symbol_tables.h"  // IWYU pragma: export
 #undef RC_PFN_DECL
 #undef RC_PFN_STR_DECL
 } iree_hal_rocm_dynamic_symbols_t;
diff --git a/experimental/rocm/dynamic_symbols_test.cc b/experimental/rocm/dynamic_symbols_test.cc
index dd33e07..06ea00a 100644
--- a/experimental/rocm/dynamic_symbols_test.cc
+++ b/experimental/rocm/dynamic_symbols_test.cc
@@ -6,8 +6,10 @@
 
 #include "experimental/rocm/dynamic_symbols.h"
 
+#include <iostream>
+
+#include "iree/base/api.h"
 #include "iree/testing/gtest.h"
-#include "iree/testing/status_matchers.h"
 
 namespace iree {
 namespace hal {
@@ -25,7 +27,7 @@
   iree_status_t status = iree_hal_rocm_dynamic_symbols_initialize(
       iree_allocator_system(), &symbols);
   if (!iree_status_is_ok(status)) {
-    IREE_LOG(WARNING) << "Symbols cannot be loaded, skipping test.";
+    std::cerr << "Symbols cannot be loaded, skipping test.";
     GTEST_SKIP();
   }
 
diff --git a/experimental/rocm/event_semaphore.c b/experimental/rocm/event_semaphore.c
index b05bafe..7fe6860 100644
--- a/experimental/rocm/event_semaphore.c
+++ b/experimental/rocm/event_semaphore.c
@@ -6,10 +6,12 @@
 
 #include "experimental/rocm/event_semaphore.h"
 
-#include "experimental/rocm/status_util.h"
+#include <stddef.h>
+
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
-typedef struct {
+typedef struct iree_hal_rocm_semaphore_t {
   iree_hal_resource_t resource;
   iree_hal_rocm_context_wrapper_t *context;
   uint64_t initial_value;
diff --git a/experimental/rocm/event_semaphore.h b/experimental/rocm/event_semaphore.h
index 87ca683..5f32492 100644
--- a/experimental/rocm/event_semaphore.h
+++ b/experimental/rocm/event_semaphore.h
@@ -7,8 +7,11 @@
 #ifndef IREE_HAL_ROCM_SEMAPHORE_H_
 #define IREE_HAL_ROCM_SEMAPHORE_H_
 
+#include <stdint.h>
+
 #include "experimental/rocm/context_wrapper.h"
 #include "experimental/rocm/status_util.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/executable_layout.c b/experimental/rocm/executable_layout.c
index ba88d32..6960cd2 100644
--- a/experimental/rocm/executable_layout.c
+++ b/experimental/rocm/executable_layout.c
@@ -6,10 +6,12 @@
 
 #include "experimental/rocm/executable_layout.h"
 
-#include "experimental/rocm/status_util.h"
+#include <stddef.h>
+
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
-typedef struct {
+typedef struct iree_hal_rocm_executable_layout_t {
   iree_hal_resource_t resource;
   iree_hal_rocm_context_wrapper_t *context;
   iree_host_size_t set_layout_count;
diff --git a/experimental/rocm/executable_layout.h b/experimental/rocm/executable_layout.h
index c0469aa..cd0fccf 100644
--- a/experimental/rocm/executable_layout.h
+++ b/experimental/rocm/executable_layout.h
@@ -8,6 +8,7 @@
 #define IREE_HAL_ROCM_EXECUTABLE_LAYOUT_H_
 
 #include "experimental/rocm/context_wrapper.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/native_executable.c b/experimental/rocm/native_executable.c
index 0f1edf2..f72bd4f 100644
--- a/experimental/rocm/native_executable.c
+++ b/experimental/rocm/native_executable.c
@@ -6,7 +6,11 @@
 
 #include "experimental/rocm/native_executable.h"
 
+#include <stddef.h>
+
+#include "experimental/rocm/dynamic_symbols.h"
 #include "experimental/rocm/status_util.h"
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
 // flatcc schemas:
@@ -14,14 +18,14 @@
 #include "iree/schemas/rocm_executable_def_reader.h"
 #include "iree/schemas/rocm_executable_def_verifier.h"
 
-typedef struct {
+typedef struct iree_hal_rocm_native_executable_function_t {
   hipFunction_t rocm_function;
   uint32_t block_size_x;
   uint32_t block_size_y;
   uint32_t block_size_z;
 } iree_hal_rocm_native_executable_function_t;
 
-typedef struct {
+typedef struct iree_hal_rocm_native_executable_t {
   iree_hal_resource_t resource;
   iree_hal_rocm_context_wrapper_t *context;
   iree_host_size_t entry_count;
diff --git a/experimental/rocm/native_executable.h b/experimental/rocm/native_executable.h
index 0b33b33..7a9229a 100644
--- a/experimental/rocm/native_executable.h
+++ b/experimental/rocm/native_executable.h
@@ -7,8 +7,11 @@
 #ifndef IREE_HAL_ROCM_NATIVE_EXECUTABLE_H_
 #define IREE_HAL_ROCM_NATIVE_EXECUTABLE_H_
 
+#include <stdint.h>
+
 #include "experimental/rocm/context_wrapper.h"
 #include "experimental/rocm/rocm_headers.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/nop_executable_cache.c b/experimental/rocm/nop_executable_cache.c
index 35ec16c..6592727 100644
--- a/experimental/rocm/nop_executable_cache.c
+++ b/experimental/rocm/nop_executable_cache.c
@@ -6,10 +6,14 @@
 
 #include "experimental/rocm/nop_executable_cache.h"
 
+#include <stdbool.h>
+#include <stddef.h>
+
 #include "experimental/rocm/native_executable.h"
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
-typedef struct {
+typedef struct iree_hal_rocm_nop_executable_cache_t {
   iree_hal_resource_t resource;
   iree_hal_rocm_context_wrapper_t *context;
 } iree_hal_rocm_nop_executable_cache_t;
diff --git a/experimental/rocm/nop_executable_cache.h b/experimental/rocm/nop_executable_cache.h
index 1262fea..d1b2fc1 100644
--- a/experimental/rocm/nop_executable_cache.h
+++ b/experimental/rocm/nop_executable_cache.h
@@ -8,6 +8,7 @@
 #define IREE_HAL_ROCM_NOP_EXECUTABLE_CACHE_H_
 
 #include "experimental/rocm/context_wrapper.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/registration/CMakeLists.txt b/experimental/rocm/registration/CMakeLists.txt
index f184333..36042bc 100644
--- a/experimental/rocm/registration/CMakeLists.txt
+++ b/experimental/rocm/registration/CMakeLists.txt
@@ -16,8 +16,9 @@
   SRCS
     "driver_module.c"
   DEPS
+    iree::base
+    iree::base::cc
     iree::base::core_headers
-    iree::base::status
     iree::base::tracing
     iree::hal
     experimental::rocm
diff --git a/experimental/rocm/registration/driver_module.c b/experimental/rocm/registration/driver_module.c
index a9df558..2db41b4 100644
--- a/experimental/rocm/registration/driver_module.c
+++ b/experimental/rocm/registration/driver_module.c
@@ -7,9 +7,10 @@
 #include "experimental/rocm/registration/driver_module.h"
 
 #include <inttypes.h>
+#include <stddef.h>
 
 #include "experimental/rocm/api.h"
-#include "iree/base/target_platform.h"
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
 #define IREE_HAL_ROCM_DRIVER_ID 0x524f434d0au  // ROCM
diff --git a/experimental/rocm/registration/driver_module.h b/experimental/rocm/registration/driver_module.h
index 6c55fea..efc1a70 100644
--- a/experimental/rocm/registration/driver_module.h
+++ b/experimental/rocm/registration/driver_module.h
@@ -7,6 +7,7 @@
 #ifndef IREE_HAL_ROCM_REGISTRATION_DRIVER_MODULE_H_
 #define IREE_HAL_ROCM_REGISTRATION_DRIVER_MODULE_H_
 
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/rocm_allocator.c b/experimental/rocm/rocm_allocator.c
index c30f4f3..cccdc9f 100644
--- a/experimental/rocm/rocm_allocator.c
+++ b/experimental/rocm/rocm_allocator.c
@@ -6,11 +6,15 @@
 
 #include "experimental/rocm/rocm_allocator.h"
 
+#include <stddef.h>
+
+#include "experimental/rocm/dynamic_symbols.h"
 #include "experimental/rocm/rocm_buffer.h"
 #include "experimental/rocm/status_util.h"
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
-typedef struct iree_hal_rocm_allocator_s {
+typedef struct iree_hal_rocm_allocator_t {
   iree_hal_resource_t resource;
   iree_hal_rocm_context_wrapper_t *context;
 } iree_hal_rocm_allocator_t;
diff --git a/experimental/rocm/rocm_allocator.h b/experimental/rocm/rocm_allocator.h
index 1add674..a47480c 100644
--- a/experimental/rocm/rocm_allocator.h
+++ b/experimental/rocm/rocm_allocator.h
@@ -9,6 +9,7 @@
 
 #include "experimental/rocm/context_wrapper.h"
 #include "experimental/rocm/status_util.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/rocm_buffer.c b/experimental/rocm/rocm_buffer.c
index 5269b4f..195e897 100644
--- a/experimental/rocm/rocm_buffer.c
+++ b/experimental/rocm/rocm_buffer.c
@@ -6,11 +6,15 @@
 
 #include "experimental/rocm/rocm_buffer.h"
 
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
 #include "experimental/rocm/rocm_allocator.h"
-#include "experimental/rocm/status_util.h"
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
-typedef struct iree_hal_rocm_buffer_s {
+typedef struct iree_hal_rocm_buffer_t {
   iree_hal_buffer_t base;
   void *host_ptr;
   hipDeviceptr_t device_ptr;
diff --git a/experimental/rocm/rocm_buffer.h b/experimental/rocm/rocm_buffer.h
index 7e24bc7..e898630 100644
--- a/experimental/rocm/rocm_buffer.h
+++ b/experimental/rocm/rocm_buffer.h
@@ -8,6 +8,7 @@
 #define IREE_HAL_ROCM_BUFFER_H_
 
 #include "experimental/rocm/rocm_headers.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/rocm_device.c b/experimental/rocm/rocm_device.c
index b84d2bc..67418f6 100644
--- a/experimental/rocm/rocm_device.c
+++ b/experimental/rocm/rocm_device.c
@@ -6,7 +6,11 @@
 
 #include "experimental/rocm/rocm_device.h"
 
-#include "experimental/rocm/api.h"
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "experimental/rocm/context_wrapper.h"
 #include "experimental/rocm/descriptor_set_layout.h"
 #include "experimental/rocm/direct_command_buffer.h"
 #include "experimental/rocm/dynamic_symbols.h"
@@ -22,7 +26,7 @@
 // iree_hal_rocm_device_t
 //===----------------------------------------------------------------------===//
 
-typedef struct {
+typedef struct iree_hal_rocm_device_t {
   iree_hal_resource_t resource;
   iree_string_view_t identifier;
 
diff --git a/experimental/rocm/rocm_device.h b/experimental/rocm/rocm_device.h
index 545436d..67e2f9b 100644
--- a/experimental/rocm/rocm_device.h
+++ b/experimental/rocm/rocm_device.h
@@ -9,6 +9,7 @@
 
 #include "experimental/rocm/api.h"
 #include "experimental/rocm/dynamic_symbols.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/rocm_driver.c b/experimental/rocm/rocm_driver.c
index 4bc3323..8bd8ae6 100644
--- a/experimental/rocm/rocm_driver.c
+++ b/experimental/rocm/rocm_driver.c
@@ -4,13 +4,18 @@
 // See https://llvm.org/LICENSE.txt for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+#include <stdint.h>
+#include <string.h>
+
 #include "experimental/rocm/api.h"
 #include "experimental/rocm/dynamic_symbols.h"
 #include "experimental/rocm/rocm_device.h"
 #include "experimental/rocm/status_util.h"
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
+#include "iree/hal/api.h"
 
-typedef struct {
+typedef struct iree_hal_rocm_driver_t {
   iree_hal_resource_t resource;
   iree_allocator_t host_allocator;
   // Identifier used for the driver in the IREE driver registry.
diff --git a/experimental/rocm/rocm_event.c b/experimental/rocm/rocm_event.c
index 2d17caa..48b20c3 100644
--- a/experimental/rocm/rocm_event.c
+++ b/experimental/rocm/rocm_event.c
@@ -6,11 +6,13 @@
 
 #include "experimental/rocm/rocm_event.h"
 
-#include "experimental/rocm/status_util.h"
+#include <stddef.h>
+
+#include "iree/base/api.h"
 #include "iree/base/tracing.h"
 
 // Dummy events for now, don't do anything.
-typedef struct {
+typedef struct iree_hal_rocm_event_t {
   iree_hal_resource_t resource;
   iree_hal_rocm_context_wrapper_t *context_wrapper;
 } iree_hal_rocm_event_t;
diff --git a/experimental/rocm/rocm_event.h b/experimental/rocm/rocm_event.h
index 6c1ece6..73041a4 100644
--- a/experimental/rocm/rocm_event.h
+++ b/experimental/rocm/rocm_event.h
@@ -9,6 +9,7 @@
 
 #include "experimental/rocm/context_wrapper.h"
 #include "experimental/rocm/rocm_headers.h"
+#include "iree/base/api.h"
 #include "iree/hal/api.h"
 
 #ifdef __cplusplus
diff --git a/experimental/rocm/rocm_headers.h b/experimental/rocm/rocm_headers.h
index 056e8c0..6f5da96 100644
--- a/experimental/rocm/rocm_headers.h
+++ b/experimental/rocm/rocm_headers.h
@@ -11,6 +11,6 @@
 #error 32-bit not supported on ROCm
 #endif  // defined(IREE_PTR_SIZE_32)
 
-#include "hip/hip_runtime.h"
+#include "hip/hip_runtime.h"  // IWYU pragma: export
 
 #endif  // IREE_HAL_ROCM_ROCM_HEADERS_H_
diff --git a/experimental/rocm/status_util.c b/experimental/rocm/status_util.c
index e1b6fd6..a084c3f 100644
--- a/experimental/rocm/status_util.c
+++ b/experimental/rocm/status_util.c
@@ -6,6 +6,8 @@
 
 #include "experimental/rocm/status_util.h"
 
+#include <stddef.h>
+
 #include "experimental/rocm/dynamic_symbols.h"
 
 iree_status_t iree_hal_rocm_result_to_status(
diff --git a/experimental/rocm/status_util.h b/experimental/rocm/status_util.h
index 31e9404..b459591 100644
--- a/experimental/rocm/status_util.h
+++ b/experimental/rocm/status_util.h
@@ -7,6 +7,8 @@
 #ifndef IREE_HAL_ROCM_STATUS_UTIL_H_
 #define IREE_HAL_ROCM_STATUS_UTIL_H_
 
+#include <stdint.h>
+
 #include "experimental/rocm/dynamic_symbols.h"
 #include "iree/base/api.h"
 
diff --git a/iree/base/tracing.cc b/iree/base/tracing.cc
index 917370d..870eabd 100644
--- a/iree/base/tracing.cc
+++ b/iree/base/tracing.cc
@@ -204,8 +204,10 @@
 }  // extern "C"
 #endif  // __cplusplus
 
-#if defined(__cplusplus) && \
-    (IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_ALLOCATION_TRACKING)
+#if defined(__cplusplus) &&                                               \
+    (IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_ALLOCATION_TRACKING) && \
+    !IREE_SANITIZER_ADDRESS && !IREE_SANITIZER_MEMORY &&                  \
+    !IREE_SANITIZER_THREAD
 
 void* operator new(size_t count) noexcept {
   auto ptr = malloc(count);
diff --git a/iree/compiler/Codegen/LLVMGPU/ConvertToLLVM.cpp b/iree/compiler/Codegen/LLVMGPU/ConvertToLLVM.cpp
index 297b325..28a4dbc0 100644
--- a/iree/compiler/Codegen/LLVMGPU/ConvertToLLVM.cpp
+++ b/iree/compiler/Codegen/LLVMGPU/ConvertToLLVM.cpp
@@ -62,15 +62,49 @@
   }
 };
 
-/// Pass to test scalarization pattern.
-class TestLLVMGPUScalarizeMathOpPass
-    : public TestLLVMGPUScalarizeMathOpBase<TestLLVMGPUScalarizeMathOpPass> {
+struct ConvertSharedMemAllocOp : public OpRewritePattern<memref::AllocOp> {
+  using OpRewritePattern<memref::AllocOp>::OpRewritePattern;
+
+  LogicalResult matchAndRewrite(memref::AllocOp allocOp,
+                                PatternRewriter &rewriter) const override {
+    if (allocOp.getType().getMemorySpaceAsInt() != 3) return failure();
+    ArrayRef<int64_t> shape = allocOp.getType().getShape();
+    if (llvm::any_of(
+            shape, [](int64_t dim) { return dim == ShapedType::kDynamicSize; }))
+      return failure();
+    // In CUDA workgroup memory is represented by a global variable.
+    MemRefType allocType = allocOp.getType();
+    auto funcOp = allocOp->getParentOfType<FuncOp>();
+    auto moduleOp = funcOp->getParentOfType<ModuleOp>();
+    SymbolTable symbolTable(moduleOp);
+    OpBuilder::InsertionGuard guard(rewriter);
+    rewriter.setInsertionPoint(&moduleOp.front());
+    auto global = rewriter.create<memref::GlobalOp>(
+        funcOp.getLoc(), "__shared_memory__",
+        /*sym_visibility=*/rewriter.getStringAttr("private"),
+        /*type=*/allocType,
+        /*initial_value=*/ElementsAttr(),
+        /*constant=*/false);
+    symbolTable.insert(global);
+
+    rewriter.setInsertionPointToStart(&(*funcOp.getBody().begin()));
+    rewriter.replaceOpWithNewOp<memref::GetGlobalOp>(allocOp, global.type(),
+                                                     global.getName());
+    return success();
+  }
+};
+
+/// Pass to test in dialect transformation used to legalize the IR before
+/// convertToNVVM/ConvertToROCDL.
+class TestLLVMGPULegalizeOpPass
+    : public TestLLVMGPUScalarizeMathOpBase<TestLLVMGPULegalizeOpPass> {
   void getDependentDialects(DialectRegistry &registry) const override {
     registry.insert<vector::VectorDialect>();
   }
   void runOnOperation() override {
     OwningRewritePatternList patterns(&getContext());
     populateScalarizeMathOps(patterns);
+    populateConvertSharedMemoryAllocOps(patterns);
     (void)applyPatternsAndFoldGreedily(getOperation(), std::move(patterns));
   }
 };
@@ -290,8 +324,12 @@
       patterns.getContext());
 }
 
-std::unique_ptr<OperationPass<FuncOp>> createTestLLVMGPUScalarizeMathOpPass() {
-  return std::make_unique<TestLLVMGPUScalarizeMathOpPass>();
+void populateConvertSharedMemoryAllocOps(RewritePatternSet &patterns) {
+  patterns.add<ConvertSharedMemAllocOp>(patterns.getContext());
+}
+
+std::unique_ptr<OperationPass<ModuleOp>> createTestLLVMGPULegalizePass() {
+  return std::make_unique<TestLLVMGPULegalizeOpPass>();
 }
 
 }  // namespace iree_compiler
diff --git a/iree/compiler/Codegen/LLVMGPU/ConvertToLLVM.h b/iree/compiler/Codegen/LLVMGPU/ConvertToLLVM.h
index f639bab..62ed632 100644
--- a/iree/compiler/Codegen/LLVMGPU/ConvertToLLVM.h
+++ b/iree/compiler/Codegen/LLVMGPU/ConvertToLLVM.h
@@ -18,6 +18,9 @@
 
 void populateScalarizeMathOps(RewritePatternSet &patterns);
 
+/// Add patterns to convert AllocOp of shared memory to a global variable.
+void populateConvertSharedMemoryAllocOps(RewritePatternSet &patterns);
+
 }  // namespace iree_compiler
 }  // namespace mlir
 
diff --git a/iree/compiler/Codegen/LLVMGPU/ConvertToNVVM.cpp b/iree/compiler/Codegen/LLVMGPU/ConvertToNVVM.cpp
index 90a9976..7544522 100644
--- a/iree/compiler/Codegen/LLVMGPU/ConvertToNVVM.cpp
+++ b/iree/compiler/Codegen/LLVMGPU/ConvertToNVVM.cpp
@@ -52,6 +52,7 @@
     {
       OwningRewritePatternList patterns(&getContext());
       populateScalarizeMathOps(patterns);
+      populateConvertSharedMemoryAllocOps(patterns);
       vector::populateVectorToVectorCanonicalizationPatterns(patterns);
       vector::populateVectorContractLoweringPatterns(
           patterns,
diff --git a/iree/compiler/Codegen/LLVMGPU/ConvertToROCDL.cpp b/iree/compiler/Codegen/LLVMGPU/ConvertToROCDL.cpp
index 92cf4d2..b6cf257 100644
--- a/iree/compiler/Codegen/LLVMGPU/ConvertToROCDL.cpp
+++ b/iree/compiler/Codegen/LLVMGPU/ConvertToROCDL.cpp
@@ -52,6 +52,7 @@
     {
       OwningRewritePatternList patterns(&getContext());
       populateScalarizeMathOps(patterns);
+      populateConvertSharedMemoryAllocOps(patterns);
       vector::populateVectorToVectorCanonicalizationPatterns(patterns);
       vector::populateVectorContractLoweringPatterns(
           patterns,
diff --git a/iree/compiler/Codegen/LLVMGPU/test/BUILD b/iree/compiler/Codegen/LLVMGPU/test/BUILD
index 831c607..e291d59 100644
--- a/iree/compiler/Codegen/LLVMGPU/test/BUILD
+++ b/iree/compiler/Codegen/LLVMGPU/test/BUILD
@@ -25,7 +25,7 @@
             "nvvm_pipeline_test.mlir",
             "remove_loops.mlir",
             "rocdl_pipeline_test.mlir",
-            "scalarize.mlir",
+            "legalize.mlir",
             "vectorization.mlir",
         ],
         include = ["*.mlir"],
diff --git a/iree/compiler/Codegen/LLVMGPU/test/CMakeLists.txt b/iree/compiler/Codegen/LLVMGPU/test/CMakeLists.txt
index b18ba76..51cb48c 100644
--- a/iree/compiler/Codegen/LLVMGPU/test/CMakeLists.txt
+++ b/iree/compiler/Codegen/LLVMGPU/test/CMakeLists.txt
@@ -17,10 +17,10 @@
     "convert_to_nvvm.mlir"
     "convert_to_rocdl.mlir"
     "distribute_to_thread.mlir"
+    "legalize.mlir"
     "nvvm_pipeline_test.mlir"
     "remove_loops.mlir"
     "rocdl_pipeline_test.mlir"
-    "scalarize.mlir"
     "vectorization.mlir"
   DATA
     iree::tools::IreeFileCheck
diff --git a/iree/compiler/Codegen/LLVMGPU/test/scalarize.mlir b/iree/compiler/Codegen/LLVMGPU/test/legalize.mlir
similarity index 79%
rename from iree/compiler/Codegen/LLVMGPU/test/scalarize.mlir
rename to iree/compiler/Codegen/LLVMGPU/test/legalize.mlir
index da17849..d2df6b9 100644
--- a/iree/compiler/Codegen/LLVMGPU/test/scalarize.mlir
+++ b/iree/compiler/Codegen/LLVMGPU/test/legalize.mlir
@@ -1,4 +1,4 @@
-// RUN: iree-opt -iree-test-llvmgpu-scalarize-math-op %s | IreeFileCheck %s
+// RUN: iree-opt -iree-test-llvmgpu-legalize-ops -split-input-file %s | IreeFileCheck %s
 
 // CHECK-LABEL: func @scalarize
 func @scalarize(
@@ -34,3 +34,16 @@
   %1 = math.powf %arg1, %arg2 : vector<2xf32>
   return %0, %1 : vector<3x1x2xf32>, vector<2xf32>
 }
+
+// -----
+
+// CHECK: memref.global "private" @__shared_memory__ : memref<16x16xf32, 3>
+// CHECK: func @allocation
+// CHECK:   %[[A:.*]] = memref.get_global @__shared_memory__ : memref<16x16xf32, 3>
+// CHECK:   memref.store %{{.*}}, %[[A]][%{{.*}}, %{{.*}}] : memref<16x16xf32, 3>
+func @allocation(%arg0: f32) {
+  %0 = memref.alloc() : memref<16x16xf32, 3>
+  %c0 = constant 0 : index
+  memref.store %arg0, %0[%c0, %c0] : memref<16x16xf32, 3>
+  return
+}
diff --git a/iree/compiler/Codegen/Passes.h b/iree/compiler/Codegen/Passes.h
index e8c09a9..c42ce1c 100644
--- a/iree/compiler/Codegen/Passes.h
+++ b/iree/compiler/Codegen/Passes.h
@@ -314,7 +314,7 @@
 // Test passes
 //------------------------------------------------------------------------------
 
-std::unique_ptr<OperationPass<FuncOp>> createTestLLVMGPUScalarizeMathOpPass();
+std::unique_ptr<OperationPass<ModuleOp>> createTestLLVMGPULegalizePass();
 
 }  // namespace iree_compiler
 }  // namespace mlir
diff --git a/iree/compiler/Codegen/Passes.td b/iree/compiler/Codegen/Passes.td
index 8fa0e9e..3029395 100644
--- a/iree/compiler/Codegen/Passes.td
+++ b/iree/compiler/Codegen/Passes.td
@@ -260,9 +260,9 @@
 //------------------------------------------------------------------------------
 
 def TestLLVMGPUScalarizeMathOp :
-    Pass<"iree-test-llvmgpu-scalarize-math-op", "FuncOp"> {
-  let summary = "Test pass for scalarization patterns.";
-  let constructor = "mlir::iree_compiler::createTestLLVMGPUScalarizeMathOpPass()";
+    Pass<"iree-test-llvmgpu-legalize-ops", "ModuleOp"> {
+  let summary = "Test pass for several legalization patterns.";
+  let constructor = "mlir::iree_compiler::createTestLLVMGPULegalizePass()";
 }
 
 #endif  // IREE_DIALECT_FLOW_PASSES
diff --git a/iree/compiler/Dialect/Flow/Transforms/BUILD b/iree/compiler/Dialect/Flow/Transforms/BUILD
index c521376..fcc530c 100644
--- a/iree/compiler/Dialect/Flow/Transforms/BUILD
+++ b/iree/compiler/Dialect/Flow/Transforms/BUILD
@@ -43,6 +43,7 @@
         "FusionOfTensorOps.cpp",
         "HoistUnstreamableOps.cpp",
         "InjectDispatchTracing.cpp",
+        "InterchangeGenericOp.cpp",
         "OutlineDispatchRegions.cpp",
         "OutlineLargeConstants.cpp",
         "PadLinalgOps.cpp",
diff --git a/iree/compiler/Dialect/Flow/Transforms/CMakeLists.txt b/iree/compiler/Dialect/Flow/Transforms/CMakeLists.txt
index 33762e2..ba6b504 100644
--- a/iree/compiler/Dialect/Flow/Transforms/CMakeLists.txt
+++ b/iree/compiler/Dialect/Flow/Transforms/CMakeLists.txt
@@ -40,6 +40,7 @@
     "FusionOfTensorOps.cpp"
     "HoistUnstreamableOps.cpp"
     "InjectDispatchTracing.cpp"
+    "InterchangeGenericOp.cpp"
     "OutlineDispatchRegions.cpp"
     "OutlineLargeConstants.cpp"
     "PadLinalgOps.cpp"
diff --git a/iree/compiler/Dialect/Flow/Transforms/FusionOfTensorOps.cpp b/iree/compiler/Dialect/Flow/Transforms/FusionOfTensorOps.cpp
index 9032285..6689134 100644
--- a/iree/compiler/Dialect/Flow/Transforms/FusionOfTensorOps.cpp
+++ b/iree/compiler/Dialect/Flow/Transforms/FusionOfTensorOps.cpp
@@ -64,6 +64,14 @@
             }
           }
 
+          // Limit the number of operands. We have hard limit (32) of bindings
+          // passing down to HAL. Set the number to be as same as the limit --
+          // IREE_HAL_MODULE_MAX_DESCRIPTOR_BINDING_COUNT.
+          constexpr int64_t kIreeMaxOperandCount = 32;
+          auto numOperands = producer.getOwner()->getNumOperands() +
+                             consumer.getOwner()->getNumOperands() - 1;
+          if (numOperands >= kIreeMaxOperandCount) return false;
+
           llvm::SmallDenseSet<Operation *, 4> numUsers;
           for (Operation *user : producer.getUsers()) {
             if (isa<linalg::GenericOp>(user)) continue;
diff --git a/iree/compiler/Dialect/Flow/Transforms/InterchangeGenericOp.cpp b/iree/compiler/Dialect/Flow/Transforms/InterchangeGenericOp.cpp
new file mode 100644
index 0000000..8748060
--- /dev/null
+++ b/iree/compiler/Dialect/Flow/Transforms/InterchangeGenericOp.cpp
@@ -0,0 +1,78 @@
+// 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
+
+//===--------------- InterchangeGenericOp.cpp -----------------------------===//
+//
+// Interchange loops in generic ops to force the reduction loops to be the most
+// inner loops.
+//
+//===----------------------------------------------------------------------===//
+
+#include "iree/compiler/Dialect/Flow/Transforms/PassDetail.h"
+#include "iree/compiler/Dialect/Flow/Transforms/Passes.h"
+#include "mlir/Dialect/Affine/IR/AffineOps.h"
+#include "mlir/Dialect/Linalg/IR/LinalgOps.h"
+#include "mlir/Dialect/Linalg/Transforms/Transforms.h"
+#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace Flow {
+
+namespace {
+
+struct GenericOpInterchangePattern
+    : public OpRewritePattern<linalg::GenericOp> {
+  using OpRewritePattern<linalg::GenericOp>::OpRewritePattern;
+  LogicalResult matchAndRewrite(linalg::GenericOp genericOp,
+                                PatternRewriter &rewriter) const override {
+    SmallVector<unsigned> interchange;
+    bool needInterchange = false;
+    unsigned numParallelLoop = genericOp.getNumParallelLoops();
+    if (numParallelLoop == 0) return failure();
+    for (auto iter : llvm::enumerate(genericOp.iterator_types())) {
+      if (isParallelIterator(iter.value())) {
+        interchange.push_back(iter.index());
+        if (iter.index() >= numParallelLoop) needInterchange = true;
+      }
+    }
+    // If all the parallel loops are outter loops skip the pattern.
+    if (!needInterchange) return failure();
+    for (auto iter : llvm::enumerate(genericOp.iterator_types())) {
+      if (isReductionIterator(iter.value()))
+        interchange.push_back(iter.index());
+    }
+    rewriter.updateRootInPlace(genericOp, [&]() {
+      interchangeGenericOp(rewriter, genericOp, interchange);
+    });
+    return success();
+  }
+};
+
+struct InterchangeGenericOpsPass
+    : public InterchangeGenericBase<InterchangeGenericOpsPass> {
+  void getDependentDialects(DialectRegistry &registry) const override {
+    registry.insert<linalg::LinalgDialect>();
+  }
+
+  void runOnOperation() override {
+    OwningRewritePatternList patterns(&getContext());
+    patterns.add<GenericOpInterchangePattern>(&getContext());
+    (void)applyPatternsAndFoldGreedily(getOperation(), std::move(patterns));
+  }
+};
+
+}  // namespace
+
+std::unique_ptr<OperationPass<FuncOp>> createInterchangeLinalgGenericPass() {
+  return std::make_unique<InterchangeGenericOpsPass>();
+}
+
+}  // namespace Flow
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/iree/compiler/Dialect/Flow/Transforms/Passes.cpp b/iree/compiler/Dialect/Flow/Transforms/Passes.cpp
index 6f2288f..2a5e18b 100644
--- a/iree/compiler/Dialect/Flow/Transforms/Passes.cpp
+++ b/iree/compiler/Dialect/Flow/Transforms/Passes.cpp
@@ -105,6 +105,7 @@
   passManager.addNestedPass<FuncOp>(
       mlir::createConvertElementwiseToLinalgPass());
   passManager.addNestedPass<FuncOp>(mlir::createLinalgFoldUnitExtentDimsPass());
+  passManager.addNestedPass<FuncOp>(createInterchangeLinalgGenericPass());
   passManager.addNestedPass<FuncOp>(mlir::createCanonicalizerPass());
   passManager.addNestedPass<FuncOp>(createFusionOfTensorOpsPass());
   passManager.addNestedPass<FuncOp>(
diff --git a/iree/compiler/Dialect/Flow/Transforms/Passes.h b/iree/compiler/Dialect/Flow/Transforms/Passes.h
index 2c1fbb5..2a58c1f 100644
--- a/iree/compiler/Dialect/Flow/Transforms/Passes.h
+++ b/iree/compiler/Dialect/Flow/Transforms/Passes.h
@@ -57,6 +57,10 @@
 /// Creates a pass to fuse Linalg operations on tensors.
 std::unique_ptr<Pass> createFusionOfTensorOpsPass();
 
+/// Create a pass to interchange generic ops to force the reduction loop to be
+/// the most inner loops.
+std::unique_ptr<OperationPass<FuncOp>> createInterchangeLinalgGenericPass();
+
 // Convert operations to equivalent flow.tensor.* ops. This is run after
 // dispatch region creation to catch operations that were left outside of
 // dispatch regions and could be represented as flow.tensor.* ops.
diff --git a/iree/compiler/Dialect/Flow/Transforms/Passes.td b/iree/compiler/Dialect/Flow/Transforms/Passes.td
index 5788193..e15d56b 100644
--- a/iree/compiler/Dialect/Flow/Transforms/Passes.td
+++ b/iree/compiler/Dialect/Flow/Transforms/Passes.td
@@ -119,4 +119,10 @@
   let constructor = "mlir::iree_compiler::IREE::Flow::createStripAndSplatConstantVariablesPass()";
 }
 
+def InterchangeGeneric :
+    Pass<"iree-flow-interchange-generic-op", "FuncOp"> {
+  let summary = "Interchange generic op loops to have all the reduction loops to be inner loops.";
+  let constructor = "mlir::iree_compiler::IREE::Flow::createInterchangeLinalgGenericPass()";
+}
+
 #endif  // IREE_DIALECT_FLOW_PASSES
diff --git a/iree/compiler/Dialect/Flow/Transforms/test/BUILD b/iree/compiler/Dialect/Flow/Transforms/test/BUILD
index 9953942..d2a70f8 100644
--- a/iree/compiler/Dialect/Flow/Transforms/test/BUILD
+++ b/iree/compiler/Dialect/Flow/Transforms/test/BUILD
@@ -29,6 +29,7 @@
             "form_streams.mlir",
             "hoist_unstreamable_ops.mlir",
             "inject_dispatch_tracing.mlir",
+            "interchange.mlir",
             "outline_dispatch_regions.mlir",
             "outline_large_constants.mlir",
             "pad_linalg_ops.mlir",
diff --git a/iree/compiler/Dialect/Flow/Transforms/test/CMakeLists.txt b/iree/compiler/Dialect/Flow/Transforms/test/CMakeLists.txt
index b43f4aa..21cdce8 100644
--- a/iree/compiler/Dialect/Flow/Transforms/test/CMakeLists.txt
+++ b/iree/compiler/Dialect/Flow/Transforms/test/CMakeLists.txt
@@ -26,6 +26,7 @@
     "form_streams.mlir"
     "hoist_unstreamable_ops.mlir"
     "inject_dispatch_tracing.mlir"
+    "interchange.mlir"
     "outline_dispatch_regions.mlir"
     "outline_large_constants.mlir"
     "pad_linalg_ops.mlir"
diff --git a/iree/compiler/Dialect/Flow/Transforms/test/interchange.mlir b/iree/compiler/Dialect/Flow/Transforms/test/interchange.mlir
new file mode 100644
index 0000000..8c9a019
--- /dev/null
+++ b/iree/compiler/Dialect/Flow/Transforms/test/interchange.mlir
@@ -0,0 +1,23 @@
+// RUN: iree-opt -iree-flow-interchange-generic-op %s | IreeFileCheck %s
+
+// CHECK: #[[MAP0:.+]] = affine_map<(d0, d1, d2, d3) -> (d0, d3, d2)>
+// CHECK: #[[MAP1:.+]] = affine_map<(d0, d1, d2, d3) -> (d3, d0, d1)>
+// CHECK: #[[MAP2:.+]] = affine_map<(d0, d1, d2, d3) -> (d2, d0, d1)>
+//      CHECK: func @interchange
+//      CHECK:   linalg.generic {indexing_maps = [#[[MAP0]], #[[MAP1]], #[[MAP2]]]
+// CHECK-SAME:   iterator_types = ["parallel", "parallel", "parallel", "reduction"]}
+func @interchange(%arg0: tensor<?x?x?xf32>, %arg1: tensor<?x?x?xf32>, %arg2: tensor<?x?x?xf32>) -> (tensor<?x?x?xf32>) {
+  %0 = linalg.generic {indexing_maps = [
+    affine_map<(d0, d1, d2, d3) -> (d1, d0, d3)>,
+    affine_map<(d0, d1, d2, d3) -> (d0, d1, d2)>,
+    affine_map<(d0, d1, d2, d3) -> (d3, d1, d2)>],
+    iterator_types = ["reduction", "parallel", "parallel", "parallel"]}
+  ins(%arg0, %arg1 : tensor<?x?x?xf32>, tensor<?x?x?xf32>)
+  outs(%arg2 : tensor<?x?x?xf32>) {
+  ^bb0(%arg3: f32, %arg4: f32, %arg5: f32):  // no predecessors
+    %m = mulf %arg3, %arg4 : f32
+    %a = addf %arg5, %m : f32
+    linalg.yield %a : f32
+  } -> tensor<?x?x?xf32>
+  return %0 : tensor<?x?x?xf32>
+}
diff --git a/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp b/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp
index 3ff4c80..0ac4bc9 100644
--- a/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp
+++ b/iree/compiler/Dialect/HAL/Conversion/HALToVM/ConvertCommandBufferOps.cpp
@@ -74,6 +74,14 @@
   patterns.insert<VMImportOpConversion<IREE::HAL::CommandBufferEndOp>>(
       context, importSymbols, typeConverter, "hal.command_buffer.end");
   patterns
+      .insert<VMImportOpConversion<IREE::HAL::CommandBufferBeginDebugGroupOp>>(
+          context, importSymbols, typeConverter,
+          "hal.command_buffer.begin_debug_group");
+  patterns
+      .insert<VMImportOpConversion<IREE::HAL::CommandBufferEndDebugGroupOp>>(
+          context, importSymbols, typeConverter,
+          "hal.command_buffer.end_debug_group");
+  patterns
       .insert<VMImportOpConversion<IREE::HAL::CommandBufferExecutionBarrierOp>>(
           context, importSymbols, typeConverter,
           "hal.command_buffer.execution_barrier");
diff --git a/iree/compiler/Dialect/HAL/IR/HALOps.td b/iree/compiler/Dialect/HAL/IR/HALOps.td
index 158182a..cc68781 100644
--- a/iree/compiler/Dialect/HAL/IR/HALOps.td
+++ b/iree/compiler/Dialect/HAL/IR/HALOps.td
@@ -1125,6 +1125,43 @@
   let hasCanonicalizer = 1;
 }
 
+def HAL_CommandBufferBeginDebugGroupOp : HAL_Op<"command_buffer.begin_debug_group"> {
+  let summary = [{pushes a command buffer debug group label}];
+  let description = [{
+    Pushes a new debug group with the given label.
+    All commands between this and a mandatory matching call to
+    `hal.command_buffer.end_debug_group` will be grouped together with the
+    given label.
+  }];
+
+  let arguments = (ins
+    HAL_CommandBuffer:$command_buffer,
+    StrAttr:$label
+  );
+
+  let assemblyFormat = [{
+    `<` $command_buffer `:` type($command_buffer) `>`
+    `label` `(` $label `)`
+    attr-dict-with-keyword
+  }];
+}
+
+def HAL_CommandBufferEndDebugGroupOp : HAL_Op<"command_buffer.end_debug_group"> {
+  let summary = [{pops a command buffer debug group label}];
+  let description = [{
+    Pops a debug group from the stack.
+  }];
+
+  let arguments = (ins
+    HAL_CommandBuffer:$command_buffer
+  );
+
+  let assemblyFormat = [{
+    `<` $command_buffer `:` type($command_buffer) `>`
+    attr-dict-with-keyword
+  }];
+}
+
 def HAL_CommandBufferExecutionBarrierOp : HAL_Op<"command_buffer.execution_barrier"> {
   let summary = [{command buffer execution barrier recording operation}];
   let description = [{
diff --git a/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTarget.cpp b/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTarget.cpp
index 9457a92..584f6d0 100644
--- a/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTarget.cpp
+++ b/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTarget.cpp
@@ -39,8 +39,13 @@
       llvm::cl::desc("Whether to try Linking to AMD Bitcodes"),
       llvm::cl::init(false));
 
+  static llvm::cl::opt<std::string> clROCMBitcodeDir(
+      "iree-rocm-bc-dir", llvm::cl::desc("Directory of ROCM Bitcode"),
+      llvm::cl::init("/opt/rocm/amdgcn/bitcode"));
+
   targetOptions.ROCMTargetChip = clROCMTargetChip;
   targetOptions.ROCMLinkBC = clROCMLinkBC;
+  targetOptions.ROCMBitcodeDir = clROCMBitcodeDir;
 
   return targetOptions;
 }
@@ -150,7 +155,8 @@
 
     // Link module to Device Library
     if (options_.ROCMLinkBC)
-      LinkROCDLIfNecessary(llvmModule.get(), options_.ROCMTargetChip);
+      LinkROCDLIfNecessary(llvmModule.get(), options_.ROCMTargetChip,
+                           options_.ROCMBitcodeDir);
 
     // Serialize hsaco kernel into the binary that we will embed in the
     // final flatbuffer.
diff --git a/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTarget.h b/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTarget.h
index 5a28c06..d4aa445 100644
--- a/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTarget.h
+++ b/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTarget.h
@@ -20,6 +20,8 @@
   std::string ROCMTargetChip;
   // Whether to try Linking to AMD Bitcodes
   bool ROCMLinkBC;
+  // AMD Bitcodes Directory
+  std::string ROCMBitcodeDir;
 };
 
 ROCMTargetOptions getROCMTargetOptionsFromFlags();
@@ -29,7 +31,8 @@
     std::function<ROCMTargetOptions()> queryOptions);
 
 // Links LLVM module to ROC Device Library Bit Code
-void LinkROCDLIfNecessary(llvm::Module *module, std::string targetChip);
+void LinkROCDLIfNecessary(llvm::Module *module, std::string targetChip,
+                          std::string bitCodeDir);
 
 // Compiles ISAToHsaco Code
 std::string createHsaco(const std::string isa, StringRef name);
diff --git a/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTargetUtils.cpp b/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTargetUtils.cpp
index d4b798b..37a605b 100644
--- a/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTargetUtils.cpp
+++ b/iree/compiler/Dialect/HAL/Target/ROCM/ROCMTargetUtils.cpp
@@ -90,7 +90,8 @@
   return success();
 }
 
-static std::vector<std::string> GetROCDLPaths(std::string targetChip) {
+static std::vector<std::string> GetROCDLPaths(std::string targetChip,
+                                              std::string bitCodeDir) {
   // AMDGPU bitcodes.
   int lenOfChipPrefix = 3;
   std::string chipId = targetChip.substr(lenOfChipPrefix);
@@ -101,22 +102,22 @@
                                     "oclc_daz_opt_off.bc", chip_isa_bc});
 
   // Construct full path to ROCDL bitcode libraries.
-  std::string rocdl_dir_path = "/opt/rocm/amdgcn/bitcode";
   std::vector<std::string> result;
   std::string app = "/";
   for (auto &filename : *rocdl_filenames) {
-    result.push_back(rocdl_dir_path + app + filename);
+    result.push_back(bitCodeDir + app + filename);
   }
   return result;
 }
 
 // Links ROCm-Device-Libs into the given module if the module needs it.
-void LinkROCDLIfNecessary(llvm::Module *module, std::string targetChip) {
+void LinkROCDLIfNecessary(llvm::Module *module, std::string targetChip,
+                          std::string bitCodeDir) {
   if (!HAL::CouldNeedDeviceBitcode(*module)) {
     return;
   }
-  if (!succeeded(
-          HAL::LinkWithBitcodeVector(module, GetROCDLPaths(targetChip)))) {
+  if (!succeeded(HAL::LinkWithBitcodeVector(
+          module, GetROCDLPaths(targetChip, bitCodeDir)))) {
     llvm::WithColor::error(llvm::errs()) << "Fail to Link ROCDL.\n";
   };
 }
diff --git a/iree/compiler/Dialect/HAL/hal.imports.mlir b/iree/compiler/Dialect/HAL/hal.imports.mlir
index 2470eab..c51db3f 100644
--- a/iree/compiler/Dialect/HAL/hal.imports.mlir
+++ b/iree/compiler/Dialect/HAL/hal.imports.mlir
@@ -141,6 +141,17 @@
   %command_buffer : !vm.ref<!hal.command_buffer>
 )
 
+// Pushes a new debug group with the given |label|.
+vm.import @command_buffer.begin_debug_group(
+  %command_buffer : !vm.ref<!hal.command_buffer>,
+  %label : !vm.buffer
+)
+
+// Pops a debug group from the stack.
+vm.import @command_buffer.end_debug_group(
+  %command_buffer : !vm.ref<!hal.command_buffer>
+)
+
 // Defines an execution dependency between all commands recorded before the
 // barrier and all commands recorded after the barrier. Only the stages provided
 // will be affected.
diff --git a/iree/compiler/Dialect/LinalgExt/IR/LinalgExtOps.cpp b/iree/compiler/Dialect/LinalgExt/IR/LinalgExtOps.cpp
index eb0c002..2a9256c 100644
--- a/iree/compiler/Dialect/LinalgExt/IR/LinalgExtOps.cpp
+++ b/iree/compiler/Dialect/LinalgExt/IR/LinalgExtOps.cpp
@@ -254,7 +254,8 @@
 Operation *ScatterOp::getTiledImplementation(
     OpBuilder &builder, ValueRange outputs, ArrayRef<OpFoldResult> offsets,
     ArrayRef<OpFoldResult> sizes,
-    SmallVectorImpl<SmallVector<OpFoldResult, 4>> &resultOffsets) {
+    SmallVectorImpl<SmallVector<OpFoldResult, 4>> &resultOffsets,
+    SmallVectorImpl<SmallVector<OpFoldResult, 4>> &resultSizes) {
   assert(outputs.size() == 1 && offsets.size() == 1 && sizes.size() == 1);
   Location loc = getLoc();
   auto zeroAttr = builder.getI64IntegerAttr(0);
@@ -289,7 +290,12 @@
   assert(tiledIndices && "failed to get slice of indices");
 
   resultOffsets.resize(1);
-  resultOffsets[0].resize(getUpdateType().getRank(), zeroAttr);
+  resultOffsets[0].resize(updateRank, zeroAttr);
+  resultSizes.resize(1);
+  resultSizes[0].resize(updateRank);
+  for (auto dim : llvm::seq<int64_t>(0, updateRank)) {
+    resultSizes[0][dim] = getDim(builder, loc, original(), dim);
+  }
   SmallVector<Type> resultTypes;
   if (getNumResults()) {
     resultTypes.push_back(getResultTypes()[0]);
@@ -400,7 +406,8 @@
 Operation *SortOp::getTiledImplementation(
     OpBuilder &builder, ValueRange outputs, ArrayRef<OpFoldResult> offsets,
     ArrayRef<OpFoldResult> sizes,
-    SmallVectorImpl<SmallVector<OpFoldResult, 4>> &resultOffsets) {
+    SmallVectorImpl<SmallVector<OpFoldResult, 4>> &resultOffsets,
+    SmallVectorImpl<SmallVector<OpFoldResult, 4>> &resultSizes) {
   assert(outputs.size() == this->outputs().size());
   int64_t rank = getOperandRank();
   assert(offsets.size() == static_cast<size_t>(rank) &&
@@ -410,11 +417,13 @@
   Location loc = getLoc();
   SmallVector<Value> tiledOperands(outputs.size());
   resultOffsets.resize(outputs.size());
+  resultSizes.resize(outputs.size());
   for (auto en : llvm::enumerate(outputs)) {
     tiledOperands[en.index()] =
         getSlice(builder, getLoc(), en.value(), offsets, sizes, strides);
     assert(tiledOperands[en.index()] && "failed to get slice of operand");
     resultOffsets[en.index()].assign(offsets.begin(), offsets.end());
+    resultSizes[en.index()].assign(sizes.begin(), sizes.end());
   }
   SmallVector<Type, 4> resultTypes;
   if (getNumResults()) {
diff --git a/iree/compiler/Dialect/LinalgExt/IR/TiledOpInterface.td b/iree/compiler/Dialect/LinalgExt/IR/TiledOpInterface.td
index 697acae..77f6c5f 100644
--- a/iree/compiler/Dialect/LinalgExt/IR/TiledOpInterface.td
+++ b/iree/compiler/Dialect/LinalgExt/IR/TiledOpInterface.td
@@ -49,7 +49,8 @@
             "ValueRange ":$outputs,
             "ArrayRef<OpFoldResult> ":$offsets,
             "ArrayRef<OpFoldResult> ":$sizes,
-            "SmallVectorImpl<SmallVector<OpFoldResult, 4>> &":$resultOffsets),
+            "SmallVectorImpl<SmallVector<OpFoldResult, 4>> &":$resultOffsets,
+            "SmallVectorImpl<SmallVector<OpFoldResult, 4>> &":$resultSizes),
         /*methodBody=*/"",
         /*defaultImplementation=*/[{
           return nullptr;
diff --git a/iree/compiler/Dialect/LinalgExt/Transforms/Passes.h b/iree/compiler/Dialect/LinalgExt/Transforms/Passes.h
index d84b97e..cac06fd 100644
--- a/iree/compiler/Dialect/LinalgExt/Transforms/Passes.h
+++ b/iree/compiler/Dialect/LinalgExt/Transforms/Passes.h
@@ -13,7 +13,7 @@
 namespace iree_compiler {
 namespace linalg_ext {
 
-std::unique_ptr<OperationPass<FuncOp>> createLinalgExtTilingPass();
+std::unique_ptr<OperationPass<FuncOp>> createTiledOpInterfaceTilingPass();
 
 std::unique_ptr<OperationPass<FuncOp>> createLinalgExtToLoopsPass();
 
diff --git a/iree/compiler/Dialect/LinalgExt/Transforms/Passes.td b/iree/compiler/Dialect/LinalgExt/Transforms/Passes.td
index e221e33..9497cdb 100644
--- a/iree/compiler/Dialect/LinalgExt/Transforms/Passes.td
+++ b/iree/compiler/Dialect/LinalgExt/Transforms/Passes.td
@@ -15,10 +15,10 @@
   let constructor = "mlir::iree_compiler::linalg_ext::createLinalgExtToLoopsPass()";
 }
 
-def LinalgExtTiling :
+def TiledOpInterfaceTiling :
     Pass<"iree-linalg-ext-tile", "FuncOp"> {
-  let summary = "Test pass for tiling LinalgExt ops using TiledOpInterface";
-  let constructor = "mlir::iree_compiler::linalg_ext::createLinalgExtTilingPass()";
+  let summary = "Test pass for tiling using TiledOpInterface";
+  let constructor = "mlir::iree_compiler::linalg_ext::createTiledOpInterfaceTilingPass()";
 }
 
 #endif  // IREE_DIALECT_LINALGEXT_PASSES
diff --git a/iree/compiler/Dialect/LinalgExt/Transforms/Tiling.cpp b/iree/compiler/Dialect/LinalgExt/Transforms/Tiling.cpp
index 7b4be2c..f686600 100644
--- a/iree/compiler/Dialect/LinalgExt/Transforms/Tiling.cpp
+++ b/iree/compiler/Dialect/LinalgExt/Transforms/Tiling.cpp
@@ -82,13 +82,26 @@
   return valueOrAttr.get<Value>();
 }
 
+/// Returns the constant value in `valueOrAttr` if it is not a dynamic `Value`.
+static Optional<int64_t> getConstantValue(OpFoldResult valueOrAttr) {
+  if (auto attr = valueOrAttr.dyn_cast<Attribute>()) {
+    return attr.cast<IntegerAttr>().getInt();
+  }
+  return {};
+}
+
+/// Checks if `valueOrAttr` represents a constant value `val`.
+static bool isValue(OpFoldResult valueOrAttr, int64_t val) {
+  auto attr = valueOrAttr.dyn_cast<Attribute>();
+  return attr && attr.cast<IntegerAttr>().getValue() == val;
+}
+
 /// Returns true if loop is untiled. Only checks if the value is statically
 /// zero. It is assumed that a `Value` defined by a constant op is already
 /// converted to an `IntegerAttr` of that value. So here just return true if
 /// this is an attribute with a zero value.
 static bool isUntiledLoop(OpFoldResult valueOrAttr) {
-  auto attr = valueOrAttr.dyn_cast<Attribute>();
-  return attr && attr.cast<IntegerAttr>().getValue() == 0;
+  return isValue(valueOrAttr, 0);
 }
 
 /// Generates the tiled loops and the body by invoking the interface methods of
@@ -108,22 +121,23 @@
 ///   distributed loops. It is a stack, and once an entry at the top of the
 ///   stack is used for distribution it is popped before processing the inner
 ///   loops.
-static FailureOr<TiledOp> tileLinalgExtOpImpl(
-    OpBuilder &builder, TiledOpInterface op, ValueRange outputs,
+static FailureOr<TiledOp> tileInterfaceOpImpl(
+    OpBuilder &builder, TiledOpInterface tilableOp, ValueRange outputs,
     MutableArrayRef<OpFoldResult> tileSizes, ArrayRef<StringRef> iteratorTypes,
     ArrayRef<Range> loopBounds, unsigned loopDepth,
     SmallVectorImpl<OpFoldResult> &offsets,
     ArrayRef<linalg::ProcInfo> distributionInfo) {
-  Location loc = op.getLoc();
+  Location loc = tilableOp.getLoc();
   // If this is the innermost loop, then generated the tiled implementation of
   // the op by invoking the TiledOpInterface methods.
   if (loopDepth == tileSizes.size()) {
     SmallVector<SmallVector<OpFoldResult, 4>> resultOffsets;
-    Operation *tiledOp = op.getTiledImplementation(builder, outputs, offsets,
-                                                   tileSizes, resultOffsets);
+    SmallVector<SmallVector<OpFoldResult, 4>> resultSizes;
+    Operation *tiledOp = tilableOp.getTiledImplementation(
+        builder, outputs, offsets, tileSizes, resultOffsets, resultSizes);
     if (!tiledOp) {
       return static_cast<LogicalResult>(
-          op.emitOpError("failed to get tiled implementation"));
+          tilableOp.emitOpError("failed to get tiled implementation"));
     }
     assert(tiledOp->getNumResults() == 0 ||
            (resultOffsets.size() == tiledOp->getNumResults()));
@@ -134,16 +148,13 @@
     // to be inserted into the `initValues` and returned.
     if (tiledOp->getNumResults()) {
       SmallVector<Value> results;
+      auto oneAttr = builder.getI64IntegerAttr(1);
       results.reserve(tiledOp->getNumResults());
       for (auto en : llvm::enumerate(tiledOp->getResults())) {
         Value result = en.value();
         ArrayRef<OpFoldResult> offsets(resultOffsets[en.index()]);
-        auto resultType = result.getType().cast<ShapedType>();
-        auto oneAttr = builder.getI64IntegerAttr(1);
-        SmallVector<OpFoldResult> strides(resultType.getRank(), oneAttr);
-        auto sizes = llvm::to_vector<4>(llvm::map_range(
-            llvm::seq<int64_t>(0, resultType.getRank()),
-            [&](int64_t dim) { return getDim(builder, loc, result, dim); }));
+        ArrayRef<OpFoldResult> sizes(resultSizes[en.index()]);
+        SmallVector<OpFoldResult> strides(offsets.size(), oneAttr);
         Value insert = builder.create<tensor::InsertSliceOp>(
             loc, result, outputs[en.index()], offsets, sizes, strides);
         results.push_back(insert);
@@ -160,9 +171,9 @@
     assert(matchPattern(loopBounds[loopDepth].offset, m_Zero()) &&
            "expected loop bounds to have lower bound of zero");
     tileSizes[loopDepth] = getOpFoldResult(loopBounds[loopDepth].size);
-    return tileLinalgExtOpImpl(builder, op, outputs, tileSizes, iteratorTypes,
-                               loopBounds, loopDepth + 1, offsets,
-                               distributionInfo);
+    return tileInterfaceOpImpl(builder, tilableOp, outputs, tileSizes,
+                               iteratorTypes, loopBounds, loopDepth + 1,
+                               offsets, distributionInfo);
   }
 
   // Generate an scf.for for the current loop depth.
@@ -170,7 +181,7 @@
   Value ub = loopBounds[loopDepth].size;
   if (!matchPattern(loopBounds[loopDepth].stride, m_One())) {
     return static_cast<LogicalResult>(
-        op.emitOpError("expected stride to be 1"));
+        tilableOp.emitOpError("expected stride to be 1"));
   }
   Value step = getValue(builder, loc, tileSizes[loopDepth]);
 
@@ -183,7 +194,7 @@
     distributionInfo = distributionInfo.drop_front();
   }
   FailureOr<TiledOp> innerReturnValue;
-  bool isBufferTiling = op->getNumResults() == 0;
+  bool isBufferTiling = tilableOp->getNumResults() == 0;
   ValueRange initValues(isBufferTiling ? ValueRange{} : outputs);
   auto forOp = builder.create<scf::ForOp>(
       loc, lb, ub, step, initValues,
@@ -200,9 +211,10 @@
             ValueRange{iv, getValue(builder, loc, tileSizes[loopDepth]), ub});
         tileSizes[loopDepth] = getOpFoldResult(inBoundsTileSize);
         // Recursively proceed to generate the tiled loop for the next level.
-        innerReturnValue = tileLinalgExtOpImpl(
-            b, op, (isBufferTiling ? outputs : args), tileSizes, iteratorTypes,
-            loopBounds, loopDepth + 1, offsets, distributionInfo);
+        innerReturnValue =
+            tileInterfaceOpImpl(b, tilableOp, (isBufferTiling ? outputs : args),
+                                tileSizes, iteratorTypes, loopBounds,
+                                loopDepth + 1, offsets, distributionInfo);
         if (failed(innerReturnValue)) return;
         b.create<scf::YieldOp>(loc, innerReturnValue->results);
       });
@@ -215,14 +227,14 @@
   return innerReturnValue;
 }
 
-FailureOr<TiledOp> tileLinalgExtOp(OpBuilder &b, LinalgExtOp op,
+FailureOr<TiledOp> tileInterfaceOp(OpBuilder &b, Operation *op, ValueRange dest,
                                    const linalg::LinalgTilingOptions &options) {
-  TiledOpInterface tilableOp = dyn_cast<TiledOpInterface>(op.getOperation());
+  TiledOpInterface tilableOp = dyn_cast<TiledOpInterface>(op);
   if (!tilableOp) return TiledOp{};
 
   SmallVector<StringRef> iteratorTypes = tilableOp.getLoopIteratorTypes();
   SmallVector<Value, 4> tileSizesVals =
-      options.tileSizeComputationFunction(b, tilableOp.getOperation());
+      options.tileSizeComputationFunction(b, op);
   auto zeroAttr = b.getI64IntegerAttr(0);
 
   // The actual tile sizes used converts `Value` defined as constant 0, to a
@@ -233,14 +245,14 @@
   for (auto en : llvm::enumerate(iteratorTypes)) {
     if (en.value() == getParallelIteratorTypeName()) continue;
     if (!isUntiledLoop(tileSizes[en.index()])) {
-      return static_cast<LogicalResult>(op.emitOpError(
+      return static_cast<LogicalResult>(tilableOp.emitOpError(
           "unimplemented tiling of non-parallel loop iterator type"));
     }
   }
 
   // Trivial early exit case of tile sizes being zero for all parallel loops.
   if (llvm::all_of(tileSizes, isUntiledLoop)) {
-    return TiledOp{op.getOperation(), {}, {}};
+    return TiledOp{op, {}, {}};
   }
 
   SmallVector<Range> loopBounds = tilableOp.getLoopBounds(b);
@@ -257,33 +269,130 @@
       if (iteratorTypes[i] != getParallelIteratorTypeName()) continue;
       distributedLoopRange.push_back(loopBounds[i]);
     }
-    distributionInfo =
-        options.distribution->procInfo(b, op.getLoc(), distributedLoopRange);
+    distributionInfo = options.distribution->procInfo(b, tilableOp.getLoc(),
+                                                      distributedLoopRange);
   }
 
   SmallVector<OpFoldResult> offsets;
-  return tileLinalgExtOpImpl(b, tilableOp, op.outputs(), tileSizes,
-                             iteratorTypes, loopBounds, 0, offsets,
-                             distributionInfo);
+  return tileInterfaceOpImpl(b, tilableOp, dest, tileSizes, iteratorTypes,
+                             loopBounds, 0, offsets, distributionInfo);
 }
 
 //===----------------------------------------------------------------------===//
+// Interface implementations for external operations.
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct InsertSliceTiledOpInterface
+    : public TiledOpInterface::ExternalModel<InsertSliceTiledOpInterface,
+                                             tensor::InsertSliceOp> {
+  SmallVector<StringRef> getLoopIteratorTypes(Operation *op) const {
+    auto insertSliceOp = cast<tensor::InsertSliceOp>(op);
+    return SmallVector<StringRef>(insertSliceOp.getSourceType().getRank(),
+                                  getParallelIteratorTypeName());
+  }
+
+  SmallVector<Range> getLoopBounds(Operation *op, OpBuilder &b) const {
+    auto insertSliceOp = cast<tensor::InsertSliceOp>(op);
+    Value source = insertSliceOp.source();
+    RankedTensorType sourceType = insertSliceOp.getSourceType();
+    Location loc = op->getLoc();
+    Value zero = b.create<ConstantIndexOp>(loc, 0);
+    Value one = b.create<ConstantIndexOp>(loc, 1);
+    SmallVector<Range> loopBounds(sourceType.getRank(),
+                                  Range{zero, nullptr, one});
+    for (auto dim :
+         llvm::seq<int64_t>(0, insertSliceOp.getSourceType().getRank())) {
+      loopBounds[dim].size = b.create<tensor::DimOp>(loc, source, dim);
+    }
+    return loopBounds;
+  }
+
+  Operation *getTiledImplementation(
+      Operation *op, OpBuilder &b, ValueRange outputs,
+      ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
+      SmallVectorImpl<SmallVector<OpFoldResult, 4>> &resultOffsets,
+      SmallVectorImpl<SmallVector<OpFoldResult, 4>> &resultSizes) const {
+    // Compute a subtensor of the source based on the offsets.
+    auto insertOp = cast<tensor::InsertSliceOp>(op);
+    auto opOffsets = insertOp.getMixedOffsets();
+    auto opSizes = insertOp.getMixedSizes();
+    auto opStrides = insertOp.getMixedStrides();
+    if (!llvm::all_of(opStrides, [&](OpFoldResult valueOrAttr) {
+          return isValue(valueOrAttr, 1);
+        })) {
+      op->emitOpError("unable to tile operation with non-unit stride");
+      return nullptr;
+    }
+    // The operation returned is just a tensor.extract_slice of the source with
+    // the given offsets, sizes and strides. Setting the correct result offset
+    // will make the sure the tiling algorithm will insert this slice into the
+    // correct place in the destination.
+    // The result offset is just the offset passed in plus the offset specified
+    // in the op (since all strides are checked to be 1).
+    unsigned offsetIndex = 0;
+    ArrayRef<int64_t> sourceShape = insertOp.getSourceType().getShape();
+    int64_t destRank = insertOp.getType().getRank();
+    resultOffsets.resize(1);
+    resultOffsets[0].resize(destRank);
+    resultSizes.resize(1);
+    resultSizes[0].resize(destRank);
+    Location loc = insertOp.getLoc();
+    auto zeroAttr = b.getI64IntegerAttr(0);
+    auto oneAttr = b.getI64IntegerAttr(1);
+    SmallVector<OpFoldResult> strides(offsets.size(), oneAttr);
+    for (auto opOffset : llvm::enumerate(opOffsets)) {
+      // Check for rank-reducing by checking that
+      // 1) The corresponding opSize value is 1
+      // 2) The current rank of the source is not 1.
+      // Then the opOffset is for the rank-reduced dimension. Skip.
+      unsigned opOffsetIndex = opOffset.index();
+      if (isValue(opSizes[opOffsetIndex], 1) && sourceShape[offsetIndex] != 1) {
+        resultOffsets[0][opOffsetIndex] = zeroAttr;
+        resultSizes[0][opOffsetIndex] = oneAttr;
+        continue;
+      }
+      OpFoldResult opOffsetVal = opOffset.value();
+      OpFoldResult offset = offsets[offsetIndex];
+      if (opOffsetVal.is<Attribute>() && offset.is<Attribute>()) {
+        resultOffsets[0][opOffsetIndex] = b.getI64IntegerAttr(
+            *getConstantValue(opOffsetVal) + *getConstantValue(offset));
+      } else {
+        AffineMap map = AffineMap::get(
+            1, 1, {b.getAffineDimExpr(0) + b.getAffineSymbolExpr(0)});
+        resultOffsets[0][opOffsetIndex] =
+            b.create<AffineApplyOp>(loc, map,
+                                    ValueRange{getValue(b, loc, offset),
+                                               getValue(b, loc, opOffsetVal)})
+                .getResult();
+      }
+      resultSizes[0][opOffsetIndex] = sizes[offsetIndex];
+      offsetIndex++;
+    }
+    return b.create<tensor::ExtractSliceOp>(loc, insertOp.source(), offsets,
+                                            sizes, strides);
+  }
+};
+}  // namespace
+
+//===----------------------------------------------------------------------===//
 // Patterns for tiling LinalgExtOps.
 //===----------------------------------------------------------------------===//
 
 namespace {
-/// Base pattern for tiling LinalgExtOps.
-struct LinalgExtBaseTilingPattern : public RewritePattern {
-  LinalgExtBaseTilingPattern(StringRef opName, MLIRContext *context,
-                             linalg::LinalgTilingOptions options,
-                             linalg::LinalgTransformationFilter filter =
-                                 linalg::LinalgTransformationFilter(),
-                             PatternBenefit benefit = 1)
+/// Base pattern for tiling TiledOpInterfaceOps.
+struct TiledOpInterfaceBaseTilingPattern : public RewritePattern {
+  TiledOpInterfaceBaseTilingPattern(StringRef opName, MLIRContext *context,
+                                    linalg::LinalgTilingOptions options,
+                                    linalg::LinalgTransformationFilter filter =
+                                        linalg::LinalgTransformationFilter(),
+                                    PatternBenefit benefit = 1)
       : RewritePattern(opName, benefit, context),
         filter(filter),
         options(options) {}
 
-  LogicalResult matchAndRewriteBase(Operation *op, PatternRewriter &rewriter,
+  LogicalResult matchAndRewriteBase(Operation *op, ValueRange dest,
+                                    PatternRewriter &rewriter,
                                     TiledOp &result) const;
 
  private:
@@ -294,21 +403,54 @@
 };
 
 template <typename OpTy>
-struct LinalgExtTilingPattern : public LinalgExtBaseTilingPattern {
+struct LinalgExtTilingPattern : public TiledOpInterfaceBaseTilingPattern {
   LinalgExtTilingPattern(MLIRContext *context,
                          linalg::LinalgTilingOptions options,
                          linalg::LinalgTransformationFilter filter =
                              linalg::LinalgTransformationFilter(),
                          PatternBenefit benefit = 1)
-      : LinalgExtBaseTilingPattern(OpTy::getOperationName(), context, options,
-                                   filter, benefit) {}
+      : TiledOpInterfaceBaseTilingPattern(OpTy::getOperationName(), context,
+                                          options, filter, benefit) {}
 
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
+    auto linalgExtOp = cast<LinalgExtOp>(op);
     TiledOp tiledOp;
     // Check for failure.
-    if (failed(LinalgExtBaseTilingPattern::matchAndRewriteBase(op, rewriter,
-                                                               tiledOp))) {
+    if (failed(TiledOpInterfaceBaseTilingPattern::matchAndRewriteBase(
+            op, linalgExtOp.outputs(), rewriter, tiledOp))) {
+      return failure();
+    }
+    // Check for do-nothing case.
+    if (!tiledOp.op) return failure();
+    if (tiledOp.op != op) {
+      if (tiledOp.results.empty()) {
+        rewriter.eraseOp(op);
+      } else {
+        rewriter.replaceOp(op, tiledOp.results);
+      }
+    }
+    return success();
+  }
+};
+
+struct InsertSliceTilingPattern : public TiledOpInterfaceBaseTilingPattern {
+  InsertSliceTilingPattern(MLIRContext *context,
+                           linalg::LinalgTilingOptions options,
+                           linalg::LinalgTransformationFilter filter =
+                               linalg::LinalgTransformationFilter(),
+                           PatternBenefit benefit = 1)
+      : TiledOpInterfaceBaseTilingPattern(
+            tensor::InsertSliceOp::getOperationName(), context, options, filter,
+            benefit) {}
+
+  LogicalResult matchAndRewrite(Operation *op,
+                                PatternRewriter &rewriter) const override {
+    tensor::InsertSliceOp insertSliceOp = cast<tensor::InsertSliceOp>(op);
+    TiledOp tiledOp;
+    // Check for failure.
+    if (failed(TiledOpInterfaceBaseTilingPattern::matchAndRewriteBase(
+            insertSliceOp, insertSliceOp.dest(), rewriter, tiledOp))) {
       return failure();
     }
     // Check for do-nothing case.
@@ -325,16 +467,15 @@
 };
 }  // namespace
 
-LogicalResult LinalgExtBaseTilingPattern::matchAndRewriteBase(
-    Operation *op, PatternRewriter &rewriter, TiledOp &result) const {
-  auto linalgExtOp = dyn_cast<LinalgExtOp>(op);
-  if (!linalgExtOp) return failure();
+LogicalResult TiledOpInterfaceBaseTilingPattern::matchAndRewriteBase(
+    Operation *op, ValueRange dest, PatternRewriter &rewriter,
+    TiledOp &result) const {
   if (failed(filter.checkAndNotify(rewriter, op))) return failure();
   if (failed(verifySupportedTilingOptions(rewriter, op, options))) {
     return failure();
   }
 
-  FailureOr<TiledOp> res = tileLinalgExtOp(rewriter, linalgExtOp, options);
+  FailureOr<TiledOp> res = tileInterfaceOp(rewriter, op, dest, options);
   if (failed(res)) return res;
   result = *res;
   if (result.op) {
@@ -348,7 +489,8 @@
 //===----------------------------------------------------------------------===//
 
 namespace {
-struct LinalgExtTilingPass : public LinalgExtTilingBase<LinalgExtTilingPass> {
+struct TiledOpInterfaceTilingPass
+    : public TiledOpInterfaceTilingBase<TiledOpInterfaceTilingPass> {
   void getDependentDialects(DialectRegistry &registry) const override {
     registry
         .insert<AffineDialect, IREE::Flow::FlowDialect, linalg::LinalgDialect,
@@ -356,6 +498,7 @@
                 tensor::TensorDialect, scf::SCFDialect>();
   }
 
+  LogicalResult initialize(MLIRContext *context) override;
   void runOnOperation() override;
 };
 }  // namespace
@@ -365,9 +508,17 @@
   return b.template create<OpTy>(b.getInsertionPoint()->getLoc(), dim);
 }
 
-void LinalgExtTilingPass::runOnOperation() {
+LogicalResult TiledOpInterfaceTilingPass::initialize(MLIRContext *context) {
+  tensor::InsertSliceOp::attachInterface<InsertSliceTiledOpInterface>(*context);
+  return success();
+}
+
+void TiledOpInterfaceTilingPass::runOnOperation() {
   FuncOp funcOp = getOperation();
   MLIRContext *context = funcOp.getContext();
+
+  tensor::InsertSliceOp::attachInterface<InsertSliceTiledOpInterface>(*context);
+
   RewritePatternSet patterns(context);
   patterns.add<LinalgExtTilingPattern<ScatterOp>>(
       context, linalg::LinalgTilingOptions().setTileSizes({10, 20}),
@@ -379,6 +530,7 @@
       linalg::LinalgTransformationFilter(
           Identifier::get("no_tiling_input", context),
           Identifier::get("no_tiling_output", context)));
+
   patterns.add<LinalgExtTilingPattern<SortOp>>(
       context, linalg::LinalgTilingOptions().setTileSizes({0, 20}),
       linalg::LinalgTransformationFilter(
@@ -419,13 +571,19 @@
               Identifier::get("distribute_input", context),
               Identifier::get("distribute_output", context)));
 
+  patterns.add<InsertSliceTilingPattern>(
+      context, linalg::LinalgTilingOptions().setTileSizes({10, 20}),
+      linalg::LinalgTransformationFilter(
+          Identifier::get("tiling_input", context),
+          Identifier::get("tiling_output", context)));
+
   if (failed(applyPatternsAndFoldGreedily(funcOp, std::move(patterns)))) {
     return signalPassFailure();
   }
 }
 
-std::unique_ptr<OperationPass<FuncOp>> createLinalgExtTilingPass() {
-  return std::make_unique<LinalgExtTilingPass>();
+std::unique_ptr<OperationPass<FuncOp>> createTiledOpInterfaceTilingPass() {
+  return std::make_unique<TiledOpInterfaceTilingPass>();
 }
 
 }  // namespace linalg_ext
diff --git a/iree/compiler/Dialect/LinalgExt/Transforms/Transforms.h b/iree/compiler/Dialect/LinalgExt/Transforms/Transforms.h
index 08e1a81..e251cab 100644
--- a/iree/compiler/Dialect/LinalgExt/Transforms/Transforms.h
+++ b/iree/compiler/Dialect/LinalgExt/Transforms/Transforms.h
@@ -27,7 +27,7 @@
 
 /// Main entry point for tiling LinalgExtOps using TiledOpInterface.  If the
 /// `op` does not implement the `TiledOpInterface` returns a `TiledOp{}` value.
-FailureOr<TiledOp> tileLinalgExtOp(OpBuilder &b, LinalgExtOp op,
+FailureOr<TiledOp> tileLinalgExtOp(OpBuilder &b, Operation *op, ValueRange dest,
                                    const linalg::LinalgTilingOptions &options);
 
 }  // namespace linalg_ext
diff --git a/iree/compiler/Dialect/LinalgExt/Transforms/test/tiling.mlir b/iree/compiler/Dialect/LinalgExt/Transforms/test/tiling.mlir
index b870530..5818c01 100644
--- a/iree/compiler/Dialect/LinalgExt/Transforms/test/tiling.mlir
+++ b/iree/compiler/Dialect/LinalgExt/Transforms/test/tiling.mlir
@@ -30,12 +30,12 @@
 //  CHECK-SAME:         [%[[USED_TILESIZE]], %[[D1]]]
 //       CHECK:     %[[INDEX_SLICE:.+]] = tensor.extract_slice %[[INDICES]][%[[IV]], 0]
 //  CHECK-SAME:         [%[[USED_TILESIZE]], 1]
+//   CHECK-DAG:     %[[SLICE_D0:.+]] = tensor.dim %[[ORIGINAL]], %[[C0]]
+//   CHECK-DAG:     %[[SLICE_D1:.+]] = tensor.dim %[[ORIGINAL]], %[[C1]]
 //       CHECK:     %[[SCATTER_TILE:.+]] = linalg_ext.scatter
 //  CHECK-SAME:         __internal_linalg_transform__ = "tiling_output"
 //  CHECK-SAME:         ins(%[[UPDATE_SLICE]], %[[INDEX_SLICE]]
 //  CHECK-SAME:         outs(%[[INIT]]
-//   CHECK-DAG:     %[[SLICE_D0:.+]] = tensor.dim %[[SCATTER_TILE]], %[[C0]]
-//   CHECK-DAG:     %[[SLICE_D1:.+]] = tensor.dim %[[SCATTER_TILE]], %[[C1]]
 //       CHECK:     %[[YIELD:.+]] = tensor.insert_slice %[[SCATTER_TILE]] into %[[INIT]][0, 0]
 //  CHECK-SAME:         [%[[SLICE_D0]], %[[SLICE_D1]]]
 //       CHECK:     scf.yield %[[YIELD]]
@@ -114,12 +114,12 @@
 //  CHECK-SAME:         [%[[USED_TILESIZE]], %[[D1]]]
 //       CHECK:     %[[INDEX_SLICE:.+]] = tensor.extract_slice %[[INDICES]][%[[IV]], 0]
 //  CHECK-SAME:         [%[[USED_TILESIZE]], 1]
+//   CHECK-DAG:     %[[SLICE_D0:.+]] = tensor.dim %[[ORIGINAL]], %[[C0]]
+//   CHECK-DAG:     %[[SLICE_D1:.+]] = tensor.dim %[[ORIGINAL]], %[[C1]]
 //       CHECK:     %[[SCATTER_TILE:.+]] = linalg_ext.scatter
 //  CHECK-SAME:         __internal_linalg_transform__ = "distribute_output"
 //  CHECK-SAME:         ins(%[[UPDATE_SLICE]], %[[INDEX_SLICE]]
 //  CHECK-SAME:         outs(%[[INIT]]
-//   CHECK-DAG:     %[[SLICE_D0:.+]] = tensor.dim %[[SCATTER_TILE]], %[[C0]]
-//   CHECK-DAG:     %[[SLICE_D1:.+]] = tensor.dim %[[SCATTER_TILE]], %[[C1]]
 //       CHECK:     %[[YIELD:.+]] = tensor.insert_slice %[[SCATTER_TILE]] into %[[INIT]][0, 0]
 //  CHECK-SAME:         [%[[SLICE_D0]], %[[SLICE_D1]]]
 //       CHECK:     scf.yield %[[YIELD]]
@@ -197,10 +197,8 @@
 //       CHECK:     %[[SORT_TILE:.+]] = linalg_ext.sort
 //  CHECK-SAME:         __internal_linalg_transform__ = "inner_reduce_output"
 //  CHECK-SAME:         outs(%[[OPERAND_SLICE]]
-//   CHECK-DAG:     %[[SLICE_D0:.+]] = tensor.dim %[[SORT_TILE]], %[[C0]]
-//   CHECK-DAG:     %[[SLICE_D1:.+]] = tensor.dim %[[SORT_TILE]], %[[C1]]
 //       CHECK:     %[[YIELD:.+]] = tensor.insert_slice %[[SORT_TILE]] into %[[INIT]][%[[IV]], 0]
-//  CHECK-SAME:         [%[[SLICE_D0]], %[[SLICE_D1]]]
+//  CHECK-SAME:         [%[[USED_TILESIZE]], %[[D1]]]
 //       CHECK:     scf.yield %[[YIELD]]
 //       CHECK:   return %[[RESULT]]
 
@@ -232,10 +230,8 @@
 //       CHECK:     %[[SORT_TILE:.+]] = linalg_ext.sort
 //  CHECK-SAME:         __internal_linalg_transform__ = "outer_reduce_output"
 //  CHECK-SAME:         outs(%[[OPERAND_SLICE]]
-//   CHECK-DAG:     %[[SLICE_D0:.+]] = tensor.dim %[[SORT_TILE]], %[[C0]]
-//   CHECK-DAG:     %[[SLICE_D1:.+]] = tensor.dim %[[SORT_TILE]], %[[C1]]
 //       CHECK:     %[[YIELD:.+]] = tensor.insert_slice %[[SORT_TILE]] into %[[INIT]][0, %[[IV]]]
-//  CHECK-SAME:         [%[[SLICE_D0]], %[[SLICE_D1]]]
+//  CHECK-SAME:         [%[[D0]], %[[USED_TILESIZE]]]
 //       CHECK:     scf.yield %[[YIELD]]
 //       CHECK:   return %[[RESULT]]
 
@@ -272,14 +268,10 @@
 //       CHECK:     %[[SORT_TILE:.+]]:2 = linalg_ext.sort
 //  CHECK-SAME:         __internal_linalg_transform__ = "inner_reduce_output"
 //  CHECK-SAME:         outs(%[[OPERAND1_SLICE]], %[[OPERAND2_SLICE]]
-//   CHECK-DAG:     %[[SLICE_D0:.+]] = tensor.dim %[[SORT_TILE]]#0, %[[C0]]
-//   CHECK-DAG:     %[[SLICE_D1:.+]] = tensor.dim %[[SORT_TILE]]#0, %[[C1]]
 //       CHECK:     %[[YIELD1:.+]] = tensor.insert_slice %[[SORT_TILE]]#0 into %[[INIT1]][%[[IV]], 0]
-//  CHECK-SAME:         [%[[SLICE_D0]], %[[SLICE_D1]]]
-//   CHECK-DAG:     %[[SLICE_D0_0:.+]] = tensor.dim %[[SORT_TILE]]#1, %[[C0]]
-//   CHECK-DAG:     %[[SLICE_D1_0:.+]] = tensor.dim %[[SORT_TILE]]#1, %[[C1]]
+//  CHECK-SAME:         [%[[USED_TILESIZE]], %[[D1]]]
 //       CHECK:     %[[YIELD2:.+]] = tensor.insert_slice %[[SORT_TILE]]#1 into %[[INIT2]][%[[IV]], 0]
-//  CHECK-SAME:         [%[[SLICE_D0_0]], %[[SLICE_D1_0]]]
+//  CHECK-SAME:         [%[[USED_TILESIZE]], %[[D1]]]
 //       CHECK:     scf.yield %[[YIELD1]], %[[YIELD2]]
 //       CHECK:   return %[[RESULT]]#0, %[[RESULT]]#1
 
@@ -420,3 +412,65 @@
 //       CHECK:       linalg_ext.sort
 //  CHECK-SAME:           __internal_linalg_transform__ = "distribute_output"
 //  CHECK-SAME:           outs(%[[OPERAND1_SLICE]], %[[OPERAND2_SLICE]]
+
+// -----
+
+func @slice_insert(%source :tensor<?x?xf32>, %dest: tensor<?x?xf32>,
+                   %idx0 : index, %idx1 : index) -> tensor<?x?xf32> {
+  %c0 = constant 0 : index
+  %c1 = constant 1 : index
+  %0 = tensor.dim %source, %c0 : tensor<?x?xf32>
+  %1 = tensor.dim %source, %c1 : tensor<?x?xf32>
+  %2 = tensor.insert_slice %source into %dest[%idx0, %idx1] [%0, %1] [1, 1]
+      {__internal_linalg_transform__ = "tiling_input"} : tensor<?x?xf32> into tensor<?x?xf32>
+  return %2 : tensor<?x?xf32>
+}
+//  CHECK-DAG: #[[MAP0:.+]] = affine_map<(d0)[s0, s1] -> (10, -d0 + s1)>
+//  CHECK-DAG: #[[MAP1:.+]] = affine_map<(d0)[s0, s1] -> (20, -d0 + s1)>
+//  CHECK-DAG: #[[MAP2:.+]] = affine_map<(d0)[s0] -> (d0 + s0)>
+//      CHECK: func @slice_insert(
+// CHECK-SAME:   %[[ARG0:[a-zA-Z0-9_]+]]: tensor<?x?xf32>
+// CHECK-SAME:   %[[ARG1:[a-zA-Z0-9_]+]]: tensor<?x?xf32>
+// CHECK-SAME:   %[[ARG2:[a-zA-Z0-9_]+]]: index
+// CHECK-SAME:   %[[ARG3:[a-zA-Z0-9_]+]]: index
+//      CHECK:   %[[RESULT:.+]] = scf.for %[[IV0:[a-zA-Z0-9]+]] =
+//      CHECK:     %[[YIELD1:.+]] = scf.for %[[IV1:[a-zA-Z0-9]+]] =
+//      CHECK:       %[[OFFSET0:.+]] = affine.apply #[[MAP2]](%[[IV0]])[%[[ARG2]]]
+//      CHECK:       %[[OFFSET1:.+]] = affine.apply #[[MAP2]](%[[IV1]])[%[[ARG3]]]
+//      CHECK:       %[[SLICE:.+]] = tensor.extract_slice %[[ARG0]][%[[IV0]], %[[IV1]]]
+//      CHECK:       %[[UPDATE:.+]] = tensor.insert_slice %[[SLICE]]
+// CHECK-SAME:         into %{{.+}}[%[[OFFSET0]], %[[OFFSET1]]]
+//      CHECK:       scf.yield %[[UPDATE]]
+//      CHECK:     scf.yield %[[YIELD1]]
+//      CHECK:   return %[[RESULT]]
+
+// -----
+
+func @slice_insert_rank_reduce(%source :tensor<?x?xf32>, %dest: tensor<?x?x?xf32>,
+                   %idx0 : index, %idx1 : index) -> tensor<?x?x?xf32> {
+  %c0 = constant 0 : index
+  %c1 = constant 1 : index
+  %0 = tensor.dim %source, %c0 : tensor<?x?xf32>
+  %1 = tensor.dim %source, %c1 : tensor<?x?xf32>
+  %2 = tensor.insert_slice %source into %dest[%idx0, 0, %idx1] [%0, 1, %1] [1, 1, 1]
+      {__internal_linalg_transform__ = "tiling_input"} : tensor<?x?xf32> into tensor<?x?x?xf32>
+  return %2 : tensor<?x?x?xf32>
+}
+//  CHECK-DAG: #[[MAP0:.+]] = affine_map<(d0)[s0, s1] -> (10, -d0 + s1)>
+//  CHECK-DAG: #[[MAP1:.+]] = affine_map<(d0)[s0, s1] -> (20, -d0 + s1)>
+//  CHECK-DAG: #[[MAP2:.+]] = affine_map<(d0)[s0] -> (d0 + s0)>
+//      CHECK: func @slice_insert_rank_reduce(
+// CHECK-SAME:   %[[ARG0:[a-zA-Z0-9_]+]]: tensor<?x?xf32>
+// CHECK-SAME:   %[[ARG1:[a-zA-Z0-9_]+]]: tensor<?x?x?xf32>
+// CHECK-SAME:   %[[ARG2:[a-zA-Z0-9_]+]]: index
+// CHECK-SAME:   %[[ARG3:[a-zA-Z0-9_]+]]: index
+//      CHECK:   %[[RESULT:.+]] = scf.for %[[IV0:[a-zA-Z0-9]+]] =
+//      CHECK:     %[[YIELD1:.+]] = scf.for %[[IV1:[a-zA-Z0-9]+]] =
+//      CHECK:       %[[OFFSET0:.+]] = affine.apply #[[MAP2]](%[[IV0]])[%[[ARG2]]]
+//      CHECK:       %[[OFFSET1:.+]] = affine.apply #[[MAP2]](%[[IV1]])[%[[ARG3]]]
+//      CHECK:       %[[SLICE:.+]] = tensor.extract_slice %[[ARG0]][%[[IV0]], %[[IV1]]]
+//      CHECK:       %[[UPDATE:.+]] = tensor.insert_slice %[[SLICE]]
+// CHECK-SAME:         into %{{.+}}[%[[OFFSET0]], 0, %[[OFFSET1]]]
+//      CHECK:       scf.yield %[[UPDATE]]
+//      CHECK:     scf.yield %[[YIELD1]]
+//      CHECK:   return %[[RESULT]]
diff --git a/iree/hal/command_buffer.c b/iree/hal/command_buffer.c
index 99b4758..c416684 100644
--- a/iree/hal/command_buffer.c
+++ b/iree/hal/command_buffer.c
@@ -79,6 +79,22 @@
   return status;
 }
 
+IREE_API_EXPORT void iree_hal_command_buffer_begin_debug_group(
+    iree_hal_command_buffer_t* command_buffer, iree_string_view_t label,
+    iree_hal_label_color_t label_color,
+    const iree_hal_label_location_t* location) {
+  IREE_ASSERT_ARGUMENT(command_buffer);
+  _VTABLE_DISPATCH(command_buffer, begin_debug_group)
+  (command_buffer, label, label_color, location);
+}
+
+IREE_API_EXPORT void iree_hal_command_buffer_end_debug_group(
+    iree_hal_command_buffer_t* command_buffer) {
+  IREE_ASSERT_ARGUMENT(command_buffer);
+  _VTABLE_DISPATCH(command_buffer, end_debug_group)
+  (command_buffer);
+}
+
 IREE_API_EXPORT iree_status_t iree_hal_command_buffer_execution_barrier(
     iree_hal_command_buffer_t* command_buffer,
     iree_hal_execution_stage_t source_stage_mask,
diff --git a/iree/hal/command_buffer.h b/iree/hal/command_buffer.h
index 38d7242..82b3deb 100644
--- a/iree/hal/command_buffer.h
+++ b/iree/hal/command_buffer.h
@@ -182,6 +182,26 @@
   iree_device_size_t length;
 } iree_hal_buffer_barrier_t;
 
+// An RGBA color.
+typedef struct iree_hal_label_color_t {
+  uint8_t r;
+  uint8_t g;
+  uint8_t b;
+  uint8_t a;
+} iree_hal_label_color_t;
+
+// A source location attached to debug labels.
+typedef struct iree_hal_label_location_t {
+  iree_string_view_t file;
+  int line;
+} iree_hal_label_location_t;
+
+// An unspecified color; debugging tools are to choose their own.
+static inline iree_hal_label_color_t iree_hal_label_color_unspecified() {
+  iree_hal_label_color_t color = {0, 0, 0, 0};
+  return color;
+}
+
 // TODO(benvanik): replace with tables for iree_string_builder_*.
 #define iree_hal_command_buffer_mode_string(...) "TODO"
 //    {IREE_HAL_COMMAND_BUFFER_MODE_ONE_SHOT, "ONE_SHOT"},
@@ -262,6 +282,25 @@
 IREE_API_EXPORT iree_status_t
 iree_hal_command_buffer_end(iree_hal_command_buffer_t* command_buffer);
 
+// Pushes a new debug group with the given |label|.
+// All commands between this and a mandatory matching call to
+// iree_hal_command_buffer_end_debug_group will be grouped together with the
+// given label. If a source location is available it can be provided via
+// |location| to allow mapping back into the source program that issued the
+// commands.
+//
+// An optional RGBA color to show in the debug UI may be provided via
+// |label_color|; otherwise iree_hal_label_color_unspecified can be used to let
+// the debug tool choose.
+IREE_API_EXPORT void iree_hal_command_buffer_begin_debug_group(
+    iree_hal_command_buffer_t* command_buffer, iree_string_view_t label,
+    iree_hal_label_color_t label_color,
+    const iree_hal_label_location_t* location);
+
+// Pops a debug group from the stack.
+IREE_API_EXPORT void iree_hal_command_buffer_end_debug_group(
+    iree_hal_command_buffer_t* command_buffer);
+
 // Defines a memory dependency between commands recorded before and after the
 // barrier. One or more memory or buffer barriers can be specified to indicate
 // between which stages or buffers the dependencies exist.
@@ -468,6 +507,13 @@
   iree_status_t(IREE_API_PTR* begin)(iree_hal_command_buffer_t* command_buffer);
   iree_status_t(IREE_API_PTR* end)(iree_hal_command_buffer_t* command_buffer);
 
+  void(IREE_API_PTR* begin_debug_group)(
+      iree_hal_command_buffer_t* command_buffer, iree_string_view_t label,
+      iree_hal_label_color_t label_color,
+      const iree_hal_label_location_t* location);
+  void(IREE_API_PTR* end_debug_group)(
+      iree_hal_command_buffer_t* command_buffer);
+
   iree_status_t(IREE_API_PTR* execution_barrier)(
       iree_hal_command_buffer_t* command_buffer,
       iree_hal_execution_stage_t source_stage_mask,
diff --git a/iree/hal/cuda/graph_command_buffer.c b/iree/hal/cuda/graph_command_buffer.c
index 9b7fdc9..1b38e9d 100644
--- a/iree/hal/cuda/graph_command_buffer.c
+++ b/iree/hal/cuda/graph_command_buffer.c
@@ -164,6 +164,18 @@
   return iree_ok_status();
 }
 
+static void iree_hal_cuda_graph_command_buffer_begin_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer, iree_string_view_t label,
+    iree_hal_label_color_t label_color,
+    const iree_hal_label_location_t* location) {
+  // TODO(benvanik): tracy event stack.
+}
+
+static void iree_hal_cuda_graph_command_buffer_end_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer) {
+  // TODO(benvanik): tracy event stack.
+}
+
 static iree_status_t iree_hal_cuda_graph_command_buffer_execution_barrier(
     iree_hal_command_buffer_t* base_command_buffer,
     iree_hal_execution_stage_t source_stage_mask,
@@ -435,6 +447,9 @@
             iree_hal_cuda_graph_command_buffer_allowed_categories,
         .begin = iree_hal_cuda_graph_command_buffer_begin,
         .end = iree_hal_cuda_graph_command_buffer_end,
+        .begin_debug_group =
+            iree_hal_cuda_graph_command_buffer_begin_debug_group,
+        .end_debug_group = iree_hal_cuda_graph_command_buffer_end_debug_group,
         .execution_barrier =
             iree_hal_cuda_graph_command_buffer_execution_barrier,
         .signal_event = iree_hal_cuda_graph_command_buffer_signal_event,
diff --git a/iree/hal/local/inline_command_buffer.c b/iree/hal/local/inline_command_buffer.c
index 5cf10d1..3fd5692 100644
--- a/iree/hal/local/inline_command_buffer.c
+++ b/iree/hal/local/inline_command_buffer.c
@@ -172,6 +172,22 @@
 }
 
 //===----------------------------------------------------------------------===//
+// iree_hal_inline_command_buffer_t debug utilities
+//===----------------------------------------------------------------------===//
+
+static void iree_hal_inline_command_buffer_begin_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer, iree_string_view_t label,
+    iree_hal_label_color_t label_color,
+    const iree_hal_label_location_t* location) {
+  // TODO(benvanik): tracy event stack.
+}
+
+static void iree_hal_inline_command_buffer_end_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer) {
+  // TODO(benvanik): tracy event stack.
+}
+
+//===----------------------------------------------------------------------===//
 // iree_hal_command_buffer_execution_barrier
 //===----------------------------------------------------------------------===//
 
@@ -484,6 +500,8 @@
         .allowed_categories = iree_hal_inline_command_buffer_allowed_categories,
         .begin = iree_hal_inline_command_buffer_begin,
         .end = iree_hal_inline_command_buffer_end,
+        .begin_debug_group = iree_hal_inline_command_buffer_begin_debug_group,
+        .end_debug_group = iree_hal_inline_command_buffer_end_debug_group,
         .execution_barrier = iree_hal_inline_command_buffer_execution_barrier,
         .signal_event = iree_hal_inline_command_buffer_signal_event,
         .reset_event = iree_hal_inline_command_buffer_reset_event,
diff --git a/iree/hal/local/task_command_buffer.c b/iree/hal/local/task_command_buffer.c
index b05c86c..2e78bd1 100644
--- a/iree/hal/local/task_command_buffer.c
+++ b/iree/hal/local/task_command_buffer.c
@@ -379,6 +379,22 @@
 }
 
 //===----------------------------------------------------------------------===//
+// iree_hal_task_command_buffer_t debug utilities
+//===----------------------------------------------------------------------===//
+
+static void iree_hal_task_command_buffer_begin_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer, iree_string_view_t label,
+    iree_hal_label_color_t label_color,
+    const iree_hal_label_location_t* location) {
+  // TODO(benvanik): tracy event stack.
+}
+
+static void iree_hal_task_command_buffer_end_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer) {
+  // TODO(benvanik): tracy event stack.
+}
+
+//===----------------------------------------------------------------------===//
 // iree_hal_command_buffer_execution_barrier
 //===----------------------------------------------------------------------===//
 
@@ -896,6 +912,8 @@
         .allowed_categories = iree_hal_task_command_buffer_allowed_categories,
         .begin = iree_hal_task_command_buffer_begin,
         .end = iree_hal_task_command_buffer_end,
+        .begin_debug_group = iree_hal_task_command_buffer_begin_debug_group,
+        .end_debug_group = iree_hal_task_command_buffer_end_debug_group,
         .execution_barrier = iree_hal_task_command_buffer_execution_barrier,
         .signal_event = iree_hal_task_command_buffer_signal_event,
         .reset_event = iree_hal_task_command_buffer_reset_event,
diff --git a/iree/hal/vulkan/direct_command_buffer.cc b/iree/hal/vulkan/direct_command_buffer.cc
index 720ff14..96f8eed 100644
--- a/iree/hal/vulkan/direct_command_buffer.cc
+++ b/iree/hal/vulkan/direct_command_buffer.cc
@@ -184,6 +184,13 @@
                          command_buffer->handle, &begin_info),
                      "vkBeginCommandBuffer");
 
+  IREE_VULKAN_TRACE_ZONE_BEGIN_EXTERNAL(
+      command_buffer->tracing_context, command_buffer->handle,
+      /*file_name=*/NULL, 0,
+      /*line=*/0, /*func_name=*/NULL, 0,
+      "iree_hal_vulkan_direct_command_buffer",
+      strlen("iree_hal_vulkan_direct_command_buffer"));
+
   return iree_ok_status();
 }
 
@@ -192,6 +199,9 @@
   iree_hal_vulkan_direct_command_buffer_t* command_buffer =
       iree_hal_vulkan_direct_command_buffer_cast(base_command_buffer);
 
+  IREE_VULKAN_TRACE_ZONE_END(command_buffer->tracing_context,
+                             command_buffer->handle);
+
   VK_RETURN_IF_ERROR(
       command_buffer->syms->vkEndCommandBuffer(command_buffer->handle),
       "vkEndCommandBuffer");
@@ -203,6 +213,49 @@
   return iree_ok_status();
 }
 
+static void iree_hal_vulkan_direct_command_buffer_begin_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer, iree_string_view_t label,
+    iree_hal_label_color_t label_color,
+    const iree_hal_label_location_t* location) {
+  iree_hal_vulkan_direct_command_buffer_t* command_buffer =
+      iree_hal_vulkan_direct_command_buffer_cast(base_command_buffer);
+  IREE_VULKAN_TRACE_ZONE_BEGIN_EXTERNAL(
+      command_buffer->tracing_context, command_buffer->handle,
+      location ? location->file.data : NULL, location ? location->file.size : 0,
+      location ? location->line : 0, /*func_name=*/NULL, 0, label.data,
+      label.size);
+  if (command_buffer->syms->vkCmdBeginDebugUtilsLabelEXT) {
+    char label_buffer[128];
+    snprintf(label_buffer, sizeof(label_buffer), "%.*s", (int)label.size,
+             label.data);
+    VkDebugUtilsLabelEXT label_info = {
+        /*.sType=*/VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
+        /*.pNext=*/NULL,
+        /*.pLabelName=*/label_buffer,
+        /*.color=*/
+        {
+            /*r=*/label_color.r / 255.0f,
+            /*g=*/label_color.g / 255.0f,
+            /*b=*/label_color.b / 255.0f,
+            /*a=*/label_color.a / 255.0f,
+        },
+    };
+    command_buffer->syms->vkCmdBeginDebugUtilsLabelEXT(command_buffer->handle,
+                                                       &label_info);
+  }
+}
+
+static void iree_hal_vulkan_direct_command_buffer_end_debug_group(
+    iree_hal_command_buffer_t* base_command_buffer) {
+  iree_hal_vulkan_direct_command_buffer_t* command_buffer =
+      iree_hal_vulkan_direct_command_buffer_cast(base_command_buffer);
+  if (command_buffer->syms->vkCmdEndDebugUtilsLabelEXT) {
+    command_buffer->syms->vkCmdEndDebugUtilsLabelEXT(command_buffer->handle);
+  }
+  IREE_VULKAN_TRACE_ZONE_END(command_buffer->tracing_context,
+                             command_buffer->handle);
+}
+
 static VkPipelineStageFlags iree_hal_vulkan_convert_pipeline_stage_flags(
     iree_hal_execution_stage_t stage_mask) {
   VkPipelineStageFlags flags = 0;
@@ -602,8 +655,8 @@
     IREE_VULKAN_TRACE_ZONE_BEGIN_EXTERNAL(
         command_buffer->tracing_context, command_buffer->handle,
         source_location.file_name.data, source_location.file_name.size,
-        source_location.line, source_location.func_name.data,
-        source_location.func_name.size, NULL, 0);
+        source_location.line, /*func_name=*/NULL, 0,
+        source_location.func_name.data, source_location.func_name.size);
   });
 
   // Get the compiled and linked pipeline for the specified entry point and
@@ -638,8 +691,8 @@
   IREE_VULKAN_TRACE_ZONE_BEGIN_EXTERNAL(
       command_buffer->tracing_context, command_buffer->handle,
       source_location.file_name.data, source_location.file_name.size,
-      source_location.line, source_location.func_name.data,
-      source_location.func_name.size, NULL, 0);
+      source_location.line, /*func_name=*/NULL, 0,
+      source_location.func_name.data, source_location.func_name.size);
 
   // Get the compiled and linked pipeline for the specified entry point and
   // bind it to the command buffer.
@@ -671,6 +724,10 @@
         iree_hal_vulkan_direct_command_buffer_allowed_categories,
         /*.begin=*/iree_hal_vulkan_direct_command_buffer_begin,
         /*.end=*/iree_hal_vulkan_direct_command_buffer_end,
+        /*.begin_debug_group=*/
+        iree_hal_vulkan_direct_command_buffer_begin_debug_group,
+        /*.end_debug_group=*/
+        iree_hal_vulkan_direct_command_buffer_end_debug_group,
         /*.execution_barrier=*/
         iree_hal_vulkan_direct_command_buffer_execution_barrier,
         /*.signal_event=*/
diff --git a/iree/hal/vulkan/internal_vk_mem_alloc.cc b/iree/hal/vulkan/internal_vk_mem_alloc.cc
index 9fa1b27..145afd9 100644
--- a/iree/hal/vulkan/internal_vk_mem_alloc.cc
+++ b/iree/hal/vulkan/internal_vk_mem_alloc.cc
@@ -15,8 +15,9 @@
 #define VMA_ASSERT IREE_DCHECK
 #define VMA_HEAVY_ASSERT IREE_DCHECK
 
-// Use IREE_LOG for logging.
-#ifndef NDEBUG
+// NOTE: logging is disabled by default as unless you are debugging VMA itself
+// the information is not useful and just slows things down.
+#if 0
 #define VMA_DEBUG_LOG(...) _IREE_LOG_INFO << __VA_ARGS__
 #else
 #define VMA_DEBUG_LOG(...)
diff --git a/iree/modules/hal/exports.inl b/iree/modules/hal/exports.inl
index f137bd7..8d8adc3 100644
--- a/iree/modules/hal/exports.inl
+++ b/iree/modules/hal/exports.inl
@@ -41,12 +41,14 @@
 EXPORT_FN("buffer_view.trace", iree_hal_module_buffer_view_trace, rCrD, v)
 
 EXPORT_FN("command_buffer.begin", iree_hal_module_command_buffer_begin, r, v)
+EXPORT_FN("command_buffer.begin_debug_group", iree_hal_module_command_buffer_begin_debug_group, rr, v)
 EXPORT_FN("command_buffer.bind_descriptor_set", iree_hal_module_command_buffer_bind_descriptor_set, rrirCiD, v)
 EXPORT_FN("command_buffer.copy_buffer", iree_hal_module_command_buffer_copy_buffer, rririi, v)
 EXPORT_FN("command_buffer.create", iree_hal_module_command_buffer_create, rii, r)
 EXPORT_FN("command_buffer.dispatch", iree_hal_module_command_buffer_dispatch, rriiii, v)
 EXPORT_FN("command_buffer.dispatch.indirect", iree_hal_module_command_buffer_dispatch_indirect, rriri, v)
 EXPORT_FN("command_buffer.end", iree_hal_module_command_buffer_end, r, v)
+EXPORT_FN("command_buffer.end_debug_group", iree_hal_module_command_buffer_end_debug_group, r, v)
 EXPORT_FN("command_buffer.execution_barrier", iree_hal_module_command_buffer_execution_barrier, riii, v)
 EXPORT_FN("command_buffer.fill_buffer", iree_hal_module_command_buffer_fill_buffer, rriii, v)
 EXPORT_FN("command_buffer.push_constants", iree_hal_module_command_buffer_push_constants, rriCiD, v)
diff --git a/iree/modules/hal/module.c b/iree/modules/hal/module.c
index caf9a02..61ea37b 100644
--- a/iree/modules/hal/module.c
+++ b/iree/modules/hal/module.c
@@ -556,6 +556,35 @@
   return iree_hal_command_buffer_end(command_buffer);
 }
 
+IREE_VM_ABI_EXPORT(iree_hal_module_command_buffer_begin_debug_group,  //
+                   iree_hal_module_state_t,                           //
+                   rr, v) {
+  iree_hal_command_buffer_t* command_buffer = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_hal_command_buffer_check_deref(args->r0, &command_buffer));
+  iree_vm_buffer_t* label = NULL;
+  IREE_RETURN_IF_ERROR(iree_vm_buffer_check_deref(args->r1, &label));
+  iree_string_view_t label_str = iree_vm_buffer_as_string(label);
+  // TODO(benvanik): query from VM.
+  iree_hal_label_location_t location = {
+      .file = iree_string_view_empty(),
+      .line = 0,
+  };
+  iree_hal_command_buffer_begin_debug_group(
+      command_buffer, label_str, iree_hal_label_color_unspecified(), &location);
+  return iree_ok_status();
+}
+
+IREE_VM_ABI_EXPORT(iree_hal_module_command_buffer_end_debug_group,  //
+                   iree_hal_module_state_t,                         //
+                   r, v) {
+  iree_hal_command_buffer_t* command_buffer = NULL;
+  IREE_RETURN_IF_ERROR(
+      iree_hal_command_buffer_check_deref(args->r0, &command_buffer));
+  iree_hal_command_buffer_end_debug_group(command_buffer);
+  return iree_ok_status();
+}
+
 IREE_VM_ABI_EXPORT(iree_hal_module_command_buffer_execution_barrier,  //
                    iree_hal_module_state_t,                           //
                    riii, v) {
diff --git a/iree/samples/static_library/CMakeLists.txt b/iree/samples/static_library/CMakeLists.txt
new file mode 100644
index 0000000..a2f66e8
--- /dev/null
+++ b/iree/samples/static_library/CMakeLists.txt
@@ -0,0 +1,80 @@
+# 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
+
+if(NOT ${IREE_TARGET_BACKEND_DYLIB-LLVM-AOT}
+  OR NOT ${IREE_HAL_DRIVER_DYLIB}
+  OR NOT ${IREE_BUILD_COMPILER})
+  return()
+endif()
+
+# Set iree-translate binary
+set(_TRANSLATE_TOOL_EXECUTABLE $<TARGET_FILE:iree_tools_iree-translate>)
+
+# Setup args for iree-transalte
+set(_TRANSLATE_ARGS)
+list(APPEND _TRANSLATE_ARGS "-iree-mlir-to-vm-bytecode-module")
+list(APPEND _TRANSLATE_ARGS "-iree-hal-target-backends=dylib-llvm-aot")
+list(APPEND _TRANSLATE_ARGS "-iree-llvm-static-library-output-path=simple_mul.o")
+list(APPEND _TRANSLATE_ARGS "${CMAKE_CURRENT_SOURCE_DIR}/simple_mul.mlir")
+list(APPEND _TRANSLATE_ARGS "-o")
+list(APPEND _TRANSLATE_ARGS "simple_mul.vmfb")
+
+# Custom command for iree-translate to generate static library and bytecode
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/simple_mul.h
+    ${CMAKE_CURRENT_BINARY_DIR}/simple_mul.o
+    ${CMAKE_CURRENT_BINARY_DIR}/simple_mul.vmfb
+  COMMAND ${_TRANSLATE_TOOL_EXECUTABLE} ${_TRANSLATE_ARGS}
+  DEPENDS ${_TRANSLATE_TOOL_EXECUTABLE} "simple_mul.mlir"
+)
+
+# Tell cmake about the simple_mul library so it will link it
+add_library(simple_mul
+  STATIC
+  ${CMAKE_CURRENT_BINARY_DIR}/simple_mul.o)
+
+SET_TARGET_PROPERTIES(
+  simple_mul
+  PROPERTIES
+  LINKER_LANGUAGE C)
+
+# Note: If you're cross compiling the simple_mul for a different backend, you'll
+# need to run iree-translate manually to produce the desired '.vmfb' and '.h/.o'
+# files. Substitute the 'simple_mul' dependency in iree_cc_binary() below with
+# your own static library and the `simple_mul.vmfb` in the iree_c_embed_data()
+# rule. You can use paths to files, i.e. 'path/to/generated/output.vmfb' in
+# place of CMake targets.
+
+# Generate the embed data with the bytecode module
+iree_c_embed_data(
+  NAME
+    simple_mul_c
+  IDENTIFIER
+    iree_samples_static_library_simple_mul
+  GENERATED_SRCS
+    simple_mul.vmfb
+  C_FILE_OUTPUT
+    simple_mul_c.c
+  H_FILE_OUTPUT
+    simple_mul_c.h
+  FLATTEN
+  PUBLIC
+)
+
+iree_cc_binary(
+NAME
+  static_library_demo
+SRCS
+  "static_library_demo.c"
+DEPS
+  ::simple_mul_c
+  iree::runtime
+  iree::hal::local::loaders::static_library_loader
+  iree::hal::local::task_driver
+  iree::task::api
+  simple_mul
+)
diff --git a/iree/samples/static_library/README.md b/iree/samples/static_library/README.md
new file mode 100644
index 0000000..b989f0a
--- /dev/null
+++ b/iree/samples/static_library/README.md
@@ -0,0 +1,79 @@
+# IREE "Static Library" sample
+
+This sample shows how to:
+1. Produce a static library and bytecode module with IREE's compiler
+2. Compile the static library into a program using the `static_library_loader`
+3. Run the demo with the module using functions exported by the static library
+
+The model compiled into the static library exports a single function
+`simple_mul` that returns the multiplication of two tensors:
+
+```mlir
+func @simple_mul(%arg0: tensor<4xf32>, %arg1: tensor<4xf32>) -> tensor<4xf32>
+{
+  %0 = "mhlo.multiply"(%arg0, %arg1) {name = "mul.1"} : (tensor<4xf32>,
+    tensor<4xf32>) -> tensor<4xf32>
+  return %0 : tensor<4xf32>
+}
+```
+
+## Background
+
+IREE's `static_library_loader` allows applications to inject a set of static
+libraries that can be resolved at runtime by name. This can be particularly
+useful on "bare metal" or embedded systems running IREE that lack operating
+systems or the ability to load shared libraries in binaries.
+
+When static library output is enabled, `iree-translate` produces a separate
+static library to compile into the target program. At runtime bytecode module
+instructs the VM which static libraries to load exported functions from the
+model.
+
+## Instructions
+_Note: run the following commands from IREE's github repo root._
+
+1. Configure CMake for building the static library then demo. You'll need to set
+the flags building samples, the compiler, and the `dylib-llvm-aot`
+driver/backend. See
+[here](https://google.github.io/iree/building-from-source/getting-started/)
+for general instructions on building using CMake):
+
+  ```shell
+  cmake -B ../iree-build/
+    -DIREE_BUILD_SAMPLES=ON \
+    -DIREE_TARGET_BACKENDS_TO_BUILD=DYLIB-LLVM-AOT \
+    -DIREE_HAL_DRIVERS_TO_BUILD=DYLIB \
+    -DIREE_BUILD_COMPILER=ON \
+    -DCMAKE_BUILD_TYPE=RelWithDebInfo .
+  ```
+
+2. Build the `static_library_demo` CMake target to create the static demo. This
+target has several dependencies that will translate `simple_mul.mlir` into a
+static library (`simple_mul.h` & `simple_mul.c`) as well as a bytecode module
+(`simple_mul.vmfb`) which are finally built into the demo binary:
+
+  ```shell
+  cmake --build ../iree-build/ --target iree_samples_static_library_demo
+  ```
+
+3. Run the sample binary:
+
+  ```shell
+  ../iree-build/iree/samples/static_library/static_library_demo
+
+  # Output: static_library_run passed
+  ```
+
+### Changing compilation options
+
+The steps above build both the compiler for the host (machine doing the
+compiling) and the demo for the target using same options as the host machine.
+If you wish to target a different deployment other than the host, you'll need to
+compile the library and demo with different options.
+
+For example, see
+[documentation](https://google.github.io/iree/building-from-source/android/)
+on cross compiling on Android.
+
+Note: separating the target from the host will require modifying dependencies in
+the demos `CMakeLists.txt`. See included comments for more info.
diff --git a/iree/samples/static_library/simple_mul.mlir b/iree/samples/static_library/simple_mul.mlir
new file mode 100644
index 0000000..2b480bd
--- /dev/null
+++ b/iree/samples/static_library/simple_mul.mlir
@@ -0,0 +1,5 @@
+func @simple_mul(%arg0: tensor<4xf32>, %arg1: tensor<4xf32>) -> tensor<4xf32>
+{
+  %0 = "std.mulf"(%arg0, %arg1) {name = "mul.1"} : (tensor<4xf32>, tensor<4xf32>) -> tensor<4xf32>
+  return %0 : tensor<4xf32>
+}
diff --git a/iree/samples/static_library/static_library_demo.c b/iree/samples/static_library/static_library_demo.c
new file mode 100644
index 0000000..22e8127
--- /dev/null
+++ b/iree/samples/static_library/static_library_demo.c
@@ -0,0 +1,222 @@
+// 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
+
+// A example of static library loading in IREE. See the README.md for more info.
+// Note: this demo requires artifacts from iree-translate before it will run.
+
+#include <stdio.h>
+
+#include "iree/hal/local/loaders/static_library_loader.h"
+#include "iree/hal/local/task_device.h"
+#include "iree/modules/hal/module.h"
+#include "iree/runtime/api.h"
+#include "iree/samples/static_library/simple_mul_c.h"
+#include "iree/task/api.h"
+#include "iree/vm/bytecode_module.h"
+
+// Compiled static library module here to avoid IO:
+#include "iree/samples/static_library/simple_mul.h"
+
+// A function to create the HAL device from the different backend targets.
+// The HAL device is returned based on the implementation, and it must be
+// released by the caller.
+iree_status_t create_device_with_static_loader(iree_hal_device_t** device) {
+  iree_status_t status = iree_ok_status();
+  // Set paramters for the device created in the next step.
+  iree_hal_task_device_params_t params;
+  iree_hal_task_device_params_initialize(&params);
+
+  // Load the statically embedded library
+  const iree_hal_executable_library_header_t** static_library =
+      simple_mul_dispatch_0_library_query(
+          IREE_HAL_EXECUTABLE_LIBRARY_LATEST_VERSION, /*reserved=*/NULL);
+  const iree_hal_executable_library_header_t** libraries[1] = {static_library};
+
+  iree_hal_executable_loader_t* library_loader = NULL;
+  if (iree_status_is_ok(status)) {
+    status = iree_hal_static_library_loader_create(
+        IREE_ARRAYSIZE(libraries), libraries,
+        iree_hal_executable_import_provider_null(), iree_allocator_system(),
+        &library_loader);
+  }
+
+  iree_task_executor_t* executor = NULL;
+  if (iree_status_is_ok(status)) {
+    iree_task_executor_create_from_flags(iree_allocator_system(), &executor);
+  }
+
+  // Create the device and release the executor and loader afterwards.
+  if (iree_status_is_ok(status)) {
+    iree_hal_task_device_create(iree_make_cstring_view("dylib"), &params,
+                                executor, 1, &library_loader,
+                                iree_allocator_system(), device);
+  }
+  iree_task_executor_release(executor);
+  iree_hal_executable_loader_release(library_loader);
+
+  return status;
+}
+
+iree_status_t Run() {
+  iree_status_t status = iree_ok_status();
+
+  // Instance configuration (this should be shared across sessions).
+  iree_runtime_instance_options_t instance_options;
+  iree_runtime_instance_options_initialize(IREE_API_VERSION_LATEST,
+                                           &instance_options);
+  iree_runtime_instance_options_use_all_available_drivers(&instance_options);
+  iree_runtime_instance_t* instance = NULL;
+
+  if (iree_status_is_ok(status)) {
+    status = iree_runtime_instance_create(&instance_options,
+                                          iree_allocator_system(), &instance);
+  }
+
+  // Create dylib device with static loader.
+  iree_hal_device_t* device = NULL;
+  if (iree_status_is_ok(status)) {
+    status = create_device_with_static_loader(&device);
+  }
+  iree_vm_module_t* hal_module = NULL;
+  if (iree_status_is_ok(status)) {
+    status =
+        iree_hal_module_create(device, iree_allocator_system(), &hal_module);
+  }
+
+  // Session configuration (one per loaded module to hold module state).
+  iree_runtime_session_options_t session_options;
+  iree_runtime_session_options_initialize(&session_options);
+  iree_runtime_session_t* session = NULL;
+  if (iree_status_is_ok(status)) {
+    status = iree_runtime_session_create_with_device(
+        instance, &session_options, device,
+        iree_runtime_instance_host_allocator(instance), &session);
+  }
+
+  // Load bytecode module from the embedded data. Append to the session.
+  const struct iree_file_toc_t* module_file_toc =
+      iree_samples_static_library_simple_mul_create();
+  iree_const_byte_span_t module_data =
+      iree_make_const_byte_span(module_file_toc->data, module_file_toc->size);
+  iree_vm_module_t* bytecode_module = NULL;
+  if (iree_status_is_ok(status)) {
+    status = iree_vm_bytecode_module_create(module_data, iree_allocator_null(),
+                                            iree_allocator_system(),
+                                            &bytecode_module);
+  }
+  if (iree_status_is_ok(status)) {
+    status = iree_runtime_session_append_module(session, bytecode_module);
+  }
+
+  // Lookup the entry point function call.
+  const char kMainFunctionName[] = "module.simple_mul";
+  iree_runtime_call_t call;
+  memset(&call, 0, sizeof(call));
+  if (iree_status_is_ok(status)) {
+    status = iree_runtime_call_initialize_by_name(
+        session, iree_make_cstring_view(kMainFunctionName), &call);
+  }
+
+  // Populate initial values for 4 * 2 = 8.
+  const int kElementCount = 4;
+  iree_hal_dim_t shape[1] = {kElementCount};
+  iree_hal_buffer_view_t* arg0_buffer_view = NULL;
+  iree_hal_buffer_view_t* arg1_buffer_view = NULL;
+  float kFloat4[] = {4.0f, 4.0f, 4.0f, 4.0f};
+  float kFloat2[] = {2.0f, 2.0f, 2.0f, 2.0f};
+
+  iree_hal_memory_type_t input_memory_type =
+      IREE_HAL_MEMORY_TYPE_HOST_LOCAL | IREE_HAL_MEMORY_TYPE_DEVICE_VISIBLE;
+  if (iree_status_is_ok(status)) {
+    iree_hal_buffer_view_clone_heap_buffer(
+        iree_hal_device_allocator(device), shape, IREE_ARRAYSIZE(shape),
+        IREE_HAL_ELEMENT_TYPE_FLOAT_32, input_memory_type,
+        IREE_HAL_BUFFER_USAGE_ALL,
+        iree_make_const_byte_span((void*)kFloat4,
+                                  sizeof(float) * kElementCount),
+        &arg0_buffer_view);
+  }
+  if (iree_status_is_ok(status)) {
+    iree_hal_buffer_view_clone_heap_buffer(
+        iree_hal_device_allocator(device), shape, IREE_ARRAYSIZE(shape),
+        IREE_HAL_ELEMENT_TYPE_FLOAT_32, input_memory_type,
+        IREE_HAL_BUFFER_USAGE_ALL,
+        iree_make_const_byte_span((void*)kFloat2,
+                                  sizeof(float) * kElementCount),
+        &arg1_buffer_view);
+  }
+
+  // Queue buffer views for input.
+  if (iree_status_is_ok(status)) {
+    status =
+        iree_runtime_call_inputs_push_back_buffer_view(&call, arg0_buffer_view);
+  }
+  iree_hal_buffer_view_release(arg0_buffer_view);
+
+  if (iree_status_is_ok(status)) {
+    status =
+        iree_runtime_call_inputs_push_back_buffer_view(&call, arg1_buffer_view);
+  }
+  iree_hal_buffer_view_release(arg1_buffer_view);
+
+  // Invoke call.
+  if (iree_status_is_ok(status)) {
+    status = iree_runtime_call_invoke(&call, /*flags=*/0);
+  }
+
+  // Retreive output buffer view with results from the invocation.
+  iree_hal_buffer_view_t* ret_buffer_view = NULL;
+  if (iree_status_is_ok(status)) {
+    status = iree_runtime_call_outputs_pop_front_buffer_view(&call,
+                                                             &ret_buffer_view);
+  }
+
+  // Read back the results and ensure we got the right values.
+  iree_hal_buffer_mapping_t mapped_memory = {0};
+  if (iree_status_is_ok(status)) {
+    status = iree_hal_buffer_map_range(
+        iree_hal_buffer_view_buffer(ret_buffer_view),
+        IREE_HAL_MEMORY_ACCESS_READ, 0, IREE_WHOLE_BUFFER, &mapped_memory);
+  }
+  if (iree_status_is_ok(status)) {
+    if (mapped_memory.contents.data_length / sizeof(float) != kElementCount) {
+      status = iree_make_status(IREE_STATUS_UNKNOWN,
+                                "result does not match element count ");
+    }
+  }
+  if (iree_status_is_ok(status)) {
+    const float* data = (const float*)mapped_memory.contents.data;
+    for (iree_host_size_t i = 0;
+         i < mapped_memory.contents.data_length / sizeof(float); ++i) {
+      if (data[i] != 8.0f) {
+        status = iree_make_status(IREE_STATUS_UNKNOWN, "result mismatches");
+      }
+    }
+  }
+
+  // Cleanup call and buffers.
+  iree_hal_buffer_unmap_range(&mapped_memory);
+  iree_hal_buffer_view_release(ret_buffer_view);
+  iree_runtime_call_deinitialize(&call);
+
+  // Cleanup session and instance.
+  iree_hal_device_release(device);
+  iree_runtime_session_release(session);
+  iree_runtime_instance_release(instance);
+
+  return status;
+}
+
+int main() {
+  const iree_status_t result = Run();
+  if (!iree_status_is_ok(result)) {
+    iree_status_fprint(stderr, result);
+    iree_status_free(result);
+    return -1;
+  }
+  printf("static_library_run passed\n");
+  return 0;
+}
diff --git a/iree/task/task.c b/iree/task/task.c
index 160b6c1..51fc703 100644
--- a/iree/task/task.c
+++ b/iree/task/task.c
@@ -824,10 +824,6 @@
                                                    tiles_per_reservation,
                                                    iree_memory_order_relaxed);
   while (tile_base < tile_count) {
-    const uint32_t next_tile_base = iree_atomic_fetch_add_int32(
-        &shared_state->tile_index, tiles_per_reservation,
-        iree_memory_order_relaxed);
-
     const uint32_t tile_range =
         iree_min(tile_base + tiles_per_reservation, tile_count);
     for (uint32_t tile_index = tile_base; tile_index < tile_range;
@@ -865,7 +861,9 @@
       }
     }
 
-    tile_base = next_tile_base;
+    tile_base = iree_atomic_fetch_add_int32(&shared_state->tile_index,
+                                            tiles_per_reservation,
+                                            iree_memory_order_relaxed);
   }
 
   // Push aggregate statistics up to the dispatch.
diff --git a/iree/vm/stack.c b/iree/vm/stack.c
index e7005fb..eadee60 100644
--- a/iree/vm/stack.c
+++ b/iree/vm/stack.c
@@ -413,14 +413,18 @@
   stack->frame_storage_size = new_top;
   stack->top = frame_header;
 
-#if IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
-  // TODO(benvanik): cache source location and query from module.
-  iree_string_view_t function_name = iree_vm_function_name(function);
-  IREE_TRACE_ZONE_BEGIN_NAMED_DYNAMIC(z0, function_name.data,
-                                      function_name.size);
-  callee_frame->trace_zone = z0;
-  IREE_TRACE_ZONE_APPEND_VALUE(z0, frame_size);
-#endif  // IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
+  IREE_TRACE({
+    if (frame_type != IREE_VM_STACK_FRAME_NATIVE) {
+      // TODO(benvanik): cache source location and query from module.
+      iree_string_view_t function_name = iree_vm_function_name(function);
+      IREE_TRACE_ZONE_BEGIN_NAMED_DYNAMIC(z0, function_name.data,
+                                          function_name.size);
+      callee_frame->trace_zone = z0;
+      if (frame_size) {
+        IREE_TRACE_ZONE_APPEND_VALUE(z0, frame_size);
+      }
+    }
+  });
 
   if (out_callee_frame) *out_callee_frame = callee_frame;
   return iree_ok_status();
@@ -438,7 +442,11 @@
     stack->top->frame_cleanup_fn(&stack->top->frame);
   }
 
-  IREE_TRACE_ZONE_END(stack->top->frame.trace_zone);
+  IREE_TRACE({
+    if (stack->top->frame.trace_zone) {
+      IREE_TRACE_ZONE_END(stack->top->frame.trace_zone);
+    }
+  });
 
   // Restore the frame pointer to the caller.
   stack->frame_storage_size -= stack->top->frame_size;
diff --git a/scripts/ir_to_markdown.py b/scripts/ir_to_markdown.py
new file mode 100644
index 0000000..952797c
--- /dev/null
+++ b/scripts/ir_to_markdown.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+# 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
+"""Converts a dump of IR text to a markdown file with formatting.
+
+Example usage:
+  # Get a dump of IR from a compiler tool:
+  $ iree-opt \
+    -iree-transformation-pipeline \
+    -iree-hal-target-backends=vmvx \
+    -mlir-disable-threading \
+    -print-ir-after-all \
+    -print-ir-after-change \
+    -mlir-elide-elementsattrs-if-larger=8 \
+    $PWD/iree/samples/models/simple_abs.mlir \
+    2> /tmp/simple_abs_vmvx_pipeline.mlir
+    > /dev/null
+
+  # Convert the IR dump to markdown:
+  $ python3 ir_to_markdown.py \
+    /tmp/simple_abs_vmvx_pipeline.mlir \
+    -o /tmp/simple_abs_vmvx_pipeline.md
+"""
+
+import argparse
+import re
+
+MLIR_START_SEQUENCE = "// -----//"
+MLIR_END_SEQUENCE = "//----- //"
+
+
+def parse_arguments():
+  """Parses command line arguments."""
+
+  parser = argparse.ArgumentParser()
+  parser.add_argument('input_file_path',
+                      type=str,
+                      nargs='?',
+                      metavar="<input_file_path>",
+                      help='Input IR dump (.mlir from -print-ir-after-all)')
+  parser.add_argument('-o,',
+                      '--output',
+                      type=str,
+                      required=True,
+                      metavar="<output>",
+                      help='Output file path (e.g. translation_ir.md)')
+  # TODO(scotttodd): flags for original IR path and compilation command line
+  #                  .md could then show original IR + flags -> output
+  # TODO(scotttodd): flag for markdown flavor (mkdocs, github, etc.)
+  # TODO(scotttodd): flag for diff view (correlate IR before and IR after)?
+
+  return parser.parse_args()
+
+
+def main(args):
+  input_file_path = args.input_file_path
+  output_file_path = args.output
+  print("Converting input file '%s'" % (input_file_path))
+  print("     into output file '%s'" % (output_file_path))
+
+  with open(input_file_path, "r") as input_file:
+    with open(output_file_path, "w") as output_file:
+
+      # Iterate line by line through the input file, collecting text into
+      # blocks and writing them into the output file with markdown formatting
+      # as we go.
+      #
+      # Note: we could parse through and find/replace within the file using
+      # regex (or sed), but iterating this way is easier to understand and
+      # uses a predictable amount of memory.
+
+      current_block_lines = []
+      dump_after_regex = re.compile(MLIR_START_SEQUENCE + "\s(.*)\s" +
+                                    MLIR_END_SEQUENCE)
+
+      def finish_block():
+        nonlocal current_block_lines
+        if len(current_block_lines) != 0:
+          current_block_lines.append("```\n\n")
+          output_file.writelines(current_block_lines)
+          current_block_lines = []
+
+      for input_line in input_file:
+        if input_line == "\n":
+          continue
+
+        if input_line.startswith(MLIR_START_SEQUENCE):
+          finish_block()
+          header_text = dump_after_regex.match(input_line).group(1)
+          current_block_lines.append("### " + header_text + "\n\n")
+          current_block_lines.append("```mlir\n")
+        else:
+          current_block_lines.append(input_line)
+
+      finish_block()
+
+
+if __name__ == '__main__':
+  main(parse_arguments())
diff --git a/third_party/tracy b/third_party/tracy
index f03b677..50f7deb 160000
--- a/third_party/tracy
+++ b/third_party/tracy
@@ -1 +1 @@
-Subproject commit f03b677ffa0fd96fcf859c32e79b740fac7dd59e
+Subproject commit 50f7deb1a389bd3785c12fbe0be74128343f11f7
diff --git a/third_party/webgpu-headers b/third_party/webgpu-headers
new file mode 160000
index 0000000..c8e0b39
--- /dev/null
+++ b/third_party/webgpu-headers
@@ -0,0 +1 @@
+Subproject commit c8e0b39f6f6f1edded5c4adf7d46aa4d2a95befe