Finishing iree_vm_list_set_variant/iree_vm_list_push_variant impls.
diff --git a/compiler/src/iree/compiler/ConstEval/Runtime.cpp b/compiler/src/iree/compiler/ConstEval/Runtime.cpp
index d14198a..7ba471a 100644
--- a/compiler/src/iree/compiler/ConstEval/Runtime.cpp
+++ b/compiler/src/iree/compiler/ConstEval/Runtime.cpp
@@ -145,7 +145,8 @@
                                     << " got " << iree_vm_list_size(outputs);
             }
             iree_vm_variant_t variant = iree_vm_variant_empty();
-            IREE_CHECK_OK(iree_vm_list_get_variant(outputs, 0, &variant));
+            IREE_CHECK_OK(
+                iree_vm_list_get_variant_assign(outputs, 0, &variant));
             result = convertVariantToAttribute(loc, variant);
             return success(result != nullptr);
           }))) {
diff --git a/experimental/web/sample_dynamic/main.c b/experimental/web/sample_dynamic/main.c
index 17bb4be..8f75a40 100644
--- a/experimental/web/sample_dynamic/main.c
+++ b/experimental/web/sample_dynamic/main.c
@@ -283,8 +283,9 @@
   iree_vm_list_t* variants_list = iree_runtime_call_outputs(call);
   for (iree_host_size_t i = 0; i < iree_vm_list_size(variants_list); ++i) {
     iree_vm_variant_t variant = iree_vm_variant_empty();
-    IREE_RETURN_IF_ERROR(iree_vm_list_get_variant(variants_list, i, &variant),
-                         "variant %" PRIhsz " not present", i);
+    IREE_RETURN_IF_ERROR(
+        iree_vm_list_get_variant_assign(variants_list, i, &variant),
+        "variant %" PRIhsz " not present", i);
 
     if (iree_vm_variant_is_value(variant)) {
       switch (variant.type.value_type) {
diff --git a/runtime/bindings/python/vm.cc b/runtime/bindings/python/vm.cc
index 41e979b..70e5f53 100644
--- a/runtime/bindings/python/vm.cc
+++ b/runtime/bindings/python/vm.cc
@@ -255,7 +255,7 @@
 
 py::object VmVariantList::GetVariant(int index) {
   iree_vm_variant_t v = iree_vm_variant_empty();
-  CheckApiStatus(iree_vm_list_get_variant(raw_ptr(), index, &v),
+  CheckApiStatus(iree_vm_list_get_variant_assign(raw_ptr(), index, &v),
                  "Could not access list element");
   if (iree_vm_type_def_is_value(&v.type)) {
     // Convert a value type.
@@ -288,7 +288,7 @@
 
 py::object VmVariantList::GetAsSerializedTraceValue(int index) {
   iree_vm_variant_t v = iree_vm_variant_empty();
-  CheckApiStatus(iree_vm_list_get_variant(raw_ptr(), index, &v),
+  CheckApiStatus(iree_vm_list_get_variant_assign(raw_ptr(), index, &v),
                  "Could not access list element");
   if (iree_vm_type_def_is_value(&v.type)) {
     // Convert a value type.
@@ -398,7 +398,7 @@
 
 py::object VmVariantList::GetAsRef(int index) {
   iree_vm_variant_t v = iree_vm_variant_empty();
-  CheckApiStatus(iree_vm_list_get_variant(raw_ptr(), index, &v),
+  CheckApiStatus(iree_vm_list_get_variant_assign(raw_ptr(), index, &v),
                  "Could not access list element");
   if (!iree_vm_variant_is_ref(v)) {
     throw std::invalid_argument("list element is not a ref");
@@ -432,7 +432,7 @@
                         std::unordered_set<iree_vm_list_t*>& visited) {
   for (iree_host_size_t i = 0, e = iree_vm_list_size(list); i < e; ++i) {
     iree_vm_variant_t variant = iree_vm_variant_empty();
-    iree_status_t status = iree_vm_list_get_variant(list, i, &variant);
+    iree_status_t status = iree_vm_list_get_variant_assign(list, i, &variant);
     if (!iree_status_is_ok(status)) {
       iree_status_ignore(status);
       out.append("Error");
diff --git a/runtime/src/iree/tooling/comparison.cc b/runtime/src/iree/tooling/comparison.cc
index 7cc3c76..b01e3c9 100644
--- a/runtime/src/iree/tooling/comparison.cc
+++ b/runtime/src/iree/tooling/comparison.cc
@@ -244,9 +244,10 @@
   for (iree_host_size_t i = 0; i < iree_vm_list_size(expected_list); ++i) {
     iree_vm_variant_t expected_variant = iree_vm_variant_empty();
     IREE_CHECK_OK(
-        iree_vm_list_get_variant(expected_list, i, &expected_variant));
+        iree_vm_list_get_variant_assign(expected_list, i, &expected_variant));
     iree_vm_variant_t actual_variant = iree_vm_variant_empty();
-    IREE_CHECK_OK(iree_vm_list_get_variant(actual_list, i, &actual_variant));
+    IREE_CHECK_OK(
+        iree_vm_list_get_variant_assign(actual_list, i, &actual_variant));
     bool did_match = iree_tooling_compare_variants(
         (int)i, expected_variant, actual_variant, host_allocator,
         /*max_element_count=*/1024, builder);
diff --git a/runtime/src/iree/tooling/trace_replay.c b/runtime/src/iree/tooling/trace_replay.c
index 67760a1..64fe7a3 100644
--- a/runtime/src/iree/tooling/trace_replay.c
+++ b/runtime/src/iree/tooling/trace_replay.c
@@ -387,7 +387,7 @@
     iree_vm_variant_t variant = iree_vm_variant_empty();
     variant.type.value_type = IREE_VM_VALUE_TYPE_I8;
     variant.i8 = (int8_t)value;
-    return iree_vm_list_push_variant(target_list, &variant);
+    return iree_vm_list_push_variant_move(target_list, &variant);
   }
   IREE_RETURN_IF_ERROR(iree_yaml_mapping_try_find(document, value_node,
                                                   IREE_SV("i16"), &data_node));
@@ -402,7 +402,7 @@
     iree_vm_variant_t variant = iree_vm_variant_empty();
     variant.type.value_type = IREE_VM_VALUE_TYPE_I16;
     variant.i16 = (int16_t)value;
-    return iree_vm_list_push_variant(target_list, &variant);
+    return iree_vm_list_push_variant_move(target_list, &variant);
   }
   IREE_RETURN_IF_ERROR(iree_yaml_mapping_try_find(document, value_node,
                                                   IREE_SV("i32"), &data_node));
@@ -415,7 +415,7 @@
           IREE_STATUS_INVALID_ARGUMENT, "failed to parse i32 value: '%.*s'",
           (int)data_node->data.scalar.length, data_node->data.scalar.value);
     }
-    return iree_vm_list_push_variant(target_list, &variant);
+    return iree_vm_list_push_variant_move(target_list, &variant);
   }
   IREE_RETURN_IF_ERROR(iree_yaml_mapping_try_find(document, value_node,
                                                   IREE_SV("i64"), &data_node));
@@ -428,7 +428,7 @@
           IREE_STATUS_INVALID_ARGUMENT, "failed to parse i64 value: '%.*s'",
           (int)data_node->data.scalar.length, data_node->data.scalar.value);
     }
-    return iree_vm_list_push_variant(target_list, &variant);
+    return iree_vm_list_push_variant_move(target_list, &variant);
   }
   IREE_RETURN_IF_ERROR(iree_yaml_mapping_try_find(document, value_node,
                                                   IREE_SV("f32"), &data_node));
@@ -441,7 +441,7 @@
           IREE_STATUS_INVALID_ARGUMENT, "failed to parse f32 value: '%.*s'",
           (int)data_node->data.scalar.length, data_node->data.scalar.value);
     }
-    return iree_vm_list_push_variant(target_list, &variant);
+    return iree_vm_list_push_variant_move(target_list, &variant);
   }
   IREE_RETURN_IF_ERROR(iree_yaml_mapping_try_find(document, value_node,
                                                   IREE_SV("f64"), &data_node));
@@ -454,7 +454,7 @@
           IREE_STATUS_INVALID_ARGUMENT, "failed to parse f64 value: '%.*s'",
           (int)data_node->data.scalar.length, data_node->data.scalar.value);
     }
-    return iree_vm_list_push_variant(target_list, &variant);
+    return iree_vm_list_push_variant_move(target_list, &variant);
   }
   return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                           "(%zu): unimplemented scalar type parser",
@@ -937,7 +937,7 @@
   iree_string_view_t type = iree_yaml_node_as_string(type_node);
   if (iree_string_view_equal(type, IREE_SV("null"))) {
     iree_vm_variant_t null_value = iree_vm_variant_empty();
-    return iree_vm_list_push_variant(target_list, &null_value);
+    return iree_vm_list_push_variant_move(target_list, &null_value);
   } else if (iree_string_view_equal(type, IREE_SV("value"))) {
     return iree_trace_replay_parse_scalar(replay, document, value_node,
                                           target_list);
@@ -1007,7 +1007,7 @@
     iree_vm_list_t* list, iree_allocator_t host_allocator) {
   for (iree_host_size_t i = 0; i < iree_vm_list_size(list); ++i) {
     iree_vm_variant_t variant = iree_vm_variant_empty();
-    IREE_RETURN_IF_ERROR(iree_vm_list_get_variant(list, i, &variant),
+    IREE_RETURN_IF_ERROR(iree_vm_list_get_variant_assign(list, i, &variant),
                          "variant %zu not present", i);
     IREE_RETURN_IF_ERROR(
         iree_trace_replay_print_item(&variant, host_allocator));
diff --git a/runtime/src/iree/tooling/vm_util.c b/runtime/src/iree/tooling/vm_util.c
index 3052b9b..39dd4f5 100644
--- a/runtime/src/iree/tooling/vm_util.c
+++ b/runtime/src/iree/tooling/vm_util.c
@@ -376,7 +376,7 @@
   for (iree_host_size_t i = 0; i < iree_vm_list_size(list); ++i) {
     iree_vm_variant_t variant = iree_vm_variant_empty();
     IREE_RETURN_AND_END_ZONE_IF_ERROR(
-        z0, iree_vm_list_get_variant(list, i, &variant),
+        z0, iree_vm_list_get_variant_assign(list, i, &variant),
         "variant %zu not present", i);
     iree_string_builder_append_format(builder, "result[%zu]: ", i);
     IREE_RETURN_AND_END_ZONE_IF_ERROR(
@@ -482,7 +482,7 @@
   for (iree_host_size_t i = 0; i < output_strings_count; ++i) {
     iree_vm_variant_t variant = iree_vm_variant_empty();
     IREE_RETURN_AND_END_ZONE_IF_ERROR(
-        z0, iree_vm_list_get_variant(list, i, &variant));
+        z0, iree_vm_list_get_variant_assign(list, i, &variant));
     IREE_RETURN_AND_END_ZONE_IF_ERROR(
         z0, iree_tooling_output_variant(variant, output_strings[i],
                                         max_element_count, file));
diff --git a/runtime/src/iree/vm/BUILD b/runtime/src/iree/vm/BUILD
index 1fac929..3969e8d 100644
--- a/runtime/src/iree/vm/BUILD
+++ b/runtime/src/iree/vm/BUILD
@@ -75,6 +75,7 @@
         "stack.h",
         "type_def.h",
         "value.h",
+        "variant.h",
     ],
     deps = [
         "//runtime/src/iree/base",
diff --git a/runtime/src/iree/vm/CMakeLists.txt b/runtime/src/iree/vm/CMakeLists.txt
index b0447c3..48013b5 100644
--- a/runtime/src/iree/vm/CMakeLists.txt
+++ b/runtime/src/iree/vm/CMakeLists.txt
@@ -51,6 +51,7 @@
     "stack.h"
     "type_def.h"
     "value.h"
+    "variant.h"
   SRCS
     "buffer.c"
     "context.c"
diff --git a/runtime/src/iree/vm/api.h b/runtime/src/iree/vm/api.h
index 87e5750..45a0d39 100644
--- a/runtime/src/iree/vm/api.h
+++ b/runtime/src/iree/vm/api.h
@@ -20,5 +20,6 @@
 #include "iree/vm/stack.h"          // IWYU pragma: export
 #include "iree/vm/type_def.h"       // IWYU pragma: export
 #include "iree/vm/value.h"          // IWYU pragma: export
+#include "iree/vm/variant.h"        // IWYU pragma: export
 
 #endif  // IREE_VM_API_H_
diff --git a/runtime/src/iree/vm/list.c b/runtime/src/iree/vm/list.c
index 0dafa55..cc4b703 100644
--- a/runtime/src/iree/vm/list.c
+++ b/runtime/src/iree/vm/list.c
@@ -925,34 +925,60 @@
   return iree_ok_status();
 }
 
-IREE_API_EXPORT iree_status_t
-iree_vm_list_get_variant(const iree_vm_list_t* list, iree_host_size_t i,
-                         iree_vm_variant_t* out_value) {
+typedef enum {
+  IREE_VM_LIST_REF_ASSIGN = 0,
+  IREE_VM_LIST_REF_RETAIN,
+  IREE_VM_LIST_REF_MOVE,
+} iree_vm_list_ref_mode_t;
+
+static void iree_vm_list_ref_op(iree_vm_list_ref_mode_t mode,
+                                iree_vm_ref_t* ref, iree_vm_ref_t* out_ref) {
+  switch (mode) {
+    case IREE_VM_LIST_REF_ASSIGN:
+      iree_vm_ref_assign(ref, out_ref);
+      break;
+    case IREE_VM_LIST_REF_RETAIN:
+      iree_vm_ref_retain(ref, out_ref);
+      break;
+    case IREE_VM_LIST_REF_MOVE:
+      iree_vm_ref_move(ref, out_ref);
+      break;
+  }
+}
+
+static iree_status_t iree_vm_list_get_variant(const iree_vm_list_t* list,
+                                              iree_host_size_t i,
+                                              iree_vm_list_ref_mode_t ref_mode,
+                                              iree_vm_variant_t* out_variant) {
+  IREE_ASSERT_ARGUMENT(list);
+  IREE_ASSERT_ARGUMENT(out_variant);
   if (i >= list->count) {
     return iree_make_status(IREE_STATUS_OUT_OF_RANGE,
                             "index %zu out of bounds (%zu)", i, list->count);
   }
+  iree_vm_variant_reset(out_variant);
   uintptr_t element_ptr = (uintptr_t)list->storage + i * list->element_size;
   switch (list->storage_mode) {
     case IREE_VM_LIST_STORAGE_MODE_VALUE: {
-      out_value->type = list->element_type;
-      memcpy(out_value->value_storage, (void*)element_ptr, list->element_size);
+      out_variant->type = list->element_type;
+      memcpy(out_variant->value_storage, (void*)element_ptr,
+             list->element_size);
       break;
     }
     case IREE_VM_LIST_STORAGE_MODE_REF: {
       iree_vm_ref_t* element_ref = (iree_vm_ref_t*)element_ptr;
-      out_value->type.ref_type = element_ref->type;
-      out_value->type.value_type = IREE_VM_VALUE_TYPE_NONE;
-      iree_vm_ref_assign(element_ref, &out_value->ref);
+      out_variant->type.ref_type = element_ref->type;
+      out_variant->type.value_type = IREE_VM_VALUE_TYPE_NONE;
+      iree_vm_list_ref_op(ref_mode, element_ref, &out_variant->ref);
       break;
     }
     case IREE_VM_LIST_STORAGE_MODE_VARIANT: {
       iree_vm_variant_t* variant = (iree_vm_variant_t*)element_ptr;
-      out_value->type = variant->type;
+      out_variant->type = variant->type;
       if (iree_vm_type_def_is_ref(&variant->type)) {
-        iree_vm_ref_assign(&variant->ref, &out_value->ref);
+        iree_vm_list_ref_op(ref_mode, &variant->ref, &out_variant->ref);
       } else {
-        memcpy(out_value->value_storage, variant->value_storage,
+        memcpy(out_variant->value_storage, variant->value_storage,
                sizeof(variant->value_storage));
       }
       break;
@@ -963,17 +989,80 @@
   return iree_ok_status();
 }
 
-IREE_API_EXPORT iree_status_t iree_vm_list_set_variant(
-    iree_vm_list_t* list, iree_host_size_t i, const iree_vm_variant_t* value) {
-  return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
-                          "iree_vm_list_set_variant unimplemented");
+IREE_API_EXPORT iree_status_t
+iree_vm_list_get_variant_assign(const iree_vm_list_t* list, iree_host_size_t i,
+                                iree_vm_variant_t* out_variant) {
+  return iree_vm_list_get_variant(list, i, IREE_VM_LIST_REF_ASSIGN,
+                                  out_variant);
 }
 
-IREE_API_EXPORT iree_status_t iree_vm_list_push_variant(
-    iree_vm_list_t* list, const iree_vm_variant_t* value) {
+IREE_API_EXPORT iree_status_t
+iree_vm_list_get_variant_retain(const iree_vm_list_t* list, iree_host_size_t i,
+                                iree_vm_variant_t* out_variant) {
+  return iree_vm_list_get_variant(list, i, IREE_VM_LIST_REF_RETAIN,
+                                  out_variant);
+}
+
+IREE_API_EXPORT iree_status_t
+iree_vm_list_get_variant_move(const iree_vm_list_t* list, iree_host_size_t i,
+                              iree_vm_variant_t* out_variant) {
+  return iree_vm_list_get_variant(list, i, IREE_VM_LIST_REF_MOVE, out_variant);
+}
+
+static iree_status_t iree_vm_list_set_variant(iree_vm_list_t* list,
+                                              iree_host_size_t i, bool is_move,
+                                              iree_vm_variant_t* variant) {
+  if (iree_vm_type_def_is_variant(&variant->type)) {
+    iree_vm_value_t value = iree_vm_variant_value(*variant);
+    return iree_vm_list_set_value(list, i, &value);
+  } else if (iree_vm_type_def_is_value(&variant->type)) {
+    iree_vm_value_t value = {
+        .type = variant->type.value_type,
+    };
+    memcpy(value.value_storage, variant->value_storage,
+           sizeof(value.value_storage));
+    return iree_vm_list_set_value(list, i, &value);
+  } else if (iree_vm_type_def_is_ref(&variant->type)) {
+    iree_status_t status =
+        iree_vm_list_set_ref(list, i, is_move, &variant->ref);
+    if (iree_status_is_ok(status) && is_move) {
+      variant->type.ref_type = IREE_VM_REF_TYPE_NULL;
+    }
+    return status;
+  } else {
+    return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
+                            "unhandled variant value type");
+  }
+}
+
+IREE_API_EXPORT iree_status_t
+iree_vm_list_set_variant_retain(iree_vm_list_t* list, iree_host_size_t i,
+                                const iree_vm_variant_t* variant) {
+  return iree_vm_list_set_variant(list, i, /*is_move=*/false,
+                                  (iree_vm_variant_t*)variant);
+}
+
+IREE_API_EXPORT iree_status_t iree_vm_list_set_variant_move(
+    iree_vm_list_t* list, iree_host_size_t i, iree_vm_variant_t* variant) {
+  return iree_vm_list_set_variant(list, i, /*is_move=*/true, variant);
+}
+
+static iree_status_t iree_vm_list_push_variant(
+    iree_vm_list_t* list, bool is_move, const iree_vm_variant_t* variant) {
   iree_host_size_t i = iree_vm_list_size(list);
   IREE_RETURN_IF_ERROR(iree_vm_list_resize(list, i + 1));
-  return iree_vm_list_set_variant(list, i, value);
+  return iree_vm_list_set_variant(list, i, is_move,
+                                  (iree_vm_variant_t*)variant);
+}
+
+IREE_API_EXPORT iree_status_t iree_vm_list_push_variant_retain(
+    iree_vm_list_t* list, const iree_vm_variant_t* variant) {
+  return iree_vm_list_push_variant(list, /*is_move=*/false, variant);
+}
+
+IREE_API_EXPORT iree_status_t iree_vm_list_push_variant_move(
+    iree_vm_list_t* list, iree_vm_variant_t* variant) {
+  return iree_vm_list_push_variant(list, /*is_move=*/true, variant);
 }
 
 iree_status_t iree_vm_list_register_types(iree_vm_instance_t* instance) {
diff --git a/runtime/src/iree/vm/list.h b/runtime/src/iree/vm/list.h
index b7d2ba3..3b9b176 100644
--- a/runtime/src/iree/vm/list.h
+++ b/runtime/src/iree/vm/list.h
@@ -13,6 +13,7 @@
 #include "iree/vm/ref.h"
 #include "iree/vm/type_def.h"
 #include "iree/vm/value.h"
+#include "iree/vm/variant.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -202,22 +203,48 @@
 // a ref it will *not* be retained and the caller must retain it to extend its
 // lifetime.
 IREE_API_EXPORT iree_status_t
-iree_vm_list_get_variant(const iree_vm_list_t* list, iree_host_size_t i,
-                         iree_vm_variant_t* out_value);
+iree_vm_list_get_variant_assign(const iree_vm_list_t* list, iree_host_size_t i,
+                                iree_vm_variant_t* out_variant);
+
+// Returns the value of the element at the given index.
+// If the variant is a ref then it will be retained.
+IREE_API_EXPORT iree_status_t
+iree_vm_list_get_variant_retain(const iree_vm_list_t* list, iree_host_size_t i,
+                                iree_vm_variant_t* out_variant);
+
+// Returns the value of the element at the given index.
+// If the variant is a ref then it will be moved.
+IREE_API_EXPORT iree_status_t
+iree_vm_list_get_variant_move(const iree_vm_list_t* list, iree_host_size_t i,
+                              iree_vm_variant_t* out_variant);
 
 // Sets the value of the element at the given index. If the specified |value|
 // type differs from the list storage type the value will be converted using the
 // value type semantics (such as sign/zero extend, etc). If the variant is a ref
 // then it will be retained.
-IREE_API_EXPORT iree_status_t iree_vm_list_set_variant(
-    iree_vm_list_t* list, iree_host_size_t i, const iree_vm_variant_t* value);
+IREE_API_EXPORT iree_status_t iree_vm_list_set_variant_retain(
+    iree_vm_list_t* list, iree_host_size_t i, const iree_vm_variant_t* variant);
+
+// Sets the value of the element at the given index. If the specified |value|
+// type differs from the list storage type the value will be converted using the
+// value type semantics (such as sign/zero extend, etc). If the variant is a ref
+// then it will be moved.
+IREE_API_EXPORT iree_status_t iree_vm_list_set_variant_move(
+    iree_vm_list_t* list, iree_host_size_t i, iree_vm_variant_t* variant);
 
 // Pushes the value of the element to the end of the list. If the specified
-// |value| type differs from the list storage type the value will be converted
+// |variant| type differs from the list storage type the value will be converted
 // using the value type semantics (such as sign/zero extend, etc). If the
 // variant is a ref then it will be retained.
-IREE_API_EXPORT iree_status_t
-iree_vm_list_push_variant(iree_vm_list_t* list, const iree_vm_variant_t* value);
+IREE_API_EXPORT iree_status_t iree_vm_list_push_variant_retain(
+    iree_vm_list_t* list, const iree_vm_variant_t* variant);
+
+// Pushes the value of the element to the end of the list. If the specified
+// |variant| type differs from the list storage type the value will be converted
+// using the value type semantics (such as sign/zero extend, etc). If the
+// variant is a ref then it will be moved.
+IREE_API_EXPORT iree_status_t iree_vm_list_push_variant_move(
+    iree_vm_list_t* list, iree_vm_variant_t* variant);
 
 #ifdef __cplusplus
 }  // extern "C"
diff --git a/runtime/src/iree/vm/list_test.cc b/runtime/src/iree/vm/list_test.cc
index f20bc06..a18c4f5 100644
--- a/runtime/src/iree/vm/list_test.cc
+++ b/runtime/src/iree/vm/list_test.cc
@@ -101,7 +101,7 @@
   result.resize(iree_vm_list_size(list));
   for (iree_host_size_t i = 0; i < result.size(); ++i) {
     iree_vm_variant_t variant = iree_vm_variant_empty();
-    IREE_CHECK_OK(iree_vm_list_get_variant(list, i, &variant));
+    IREE_CHECK_OK(iree_vm_list_get_variant_assign(list, i, &variant));
     if (iree_vm_type_def_is_value(&variant.type)) {
       result[i].type = variant.type.value_type;
       memcpy(result[i].value_storage, variant.value_storage,
@@ -622,7 +622,7 @@
   IREE_ASSERT_OK(iree_vm_list_resize(list, 5));
   for (iree_host_size_t i = 0; i < 5; ++i) {
     iree_vm_variant_t value = iree_vm_variant_empty();
-    IREE_ASSERT_OK(iree_vm_list_get_variant(list, i, &value));
+    IREE_ASSERT_OK(iree_vm_list_get_variant_assign(list, i, &value));
     EXPECT_TRUE(iree_vm_variant_is_empty(value));
   }
 
@@ -655,7 +655,7 @@
   }
   for (iree_host_size_t i = 2; i < 5; ++i) {
     iree_vm_variant_t value = iree_vm_variant_empty();
-    IREE_ASSERT_OK(iree_vm_list_get_variant(list, i, &value));
+    IREE_ASSERT_OK(iree_vm_list_get_variant_assign(list, i, &value));
     EXPECT_TRUE(iree_vm_variant_is_empty(value));
   }
 
diff --git a/runtime/src/iree/vm/type_def.h b/runtime/src/iree/vm/type_def.h
index d8cc8b6..3de996c 100644
--- a/runtime/src/iree/vm/type_def.h
+++ b/runtime/src/iree/vm/type_def.h
@@ -58,32 +58,6 @@
   ((v)->value_type == IREE_VM_VALUE_TYPE_NONE && \
    (v)->ref_type == IREE_VM_REF_TYPE_NULL)
 
-// An variant value that can be either a primitive value type or a ref type.
-// Each variant value stores its type but users are required to check the type
-// prior to accessing any of the data.
-typedef struct iree_vm_variant_t {
-  iree_vm_type_def_t type;
-  union {
-    // TODO(benvanik): replace with iree_vm_value_t.
-    int8_t i8;
-    int16_t i16;
-    int32_t i32;
-    int64_t i64;
-    float f32;
-    double f64;
-    iree_vm_ref_t ref;
-
-    uint8_t value_storage[IREE_VM_VALUE_STORAGE_SIZE];  // max size of all value
-                                                        // types
-  };
-} iree_vm_variant_t;
-
-#define iree_vm_variant_empty() \
-  { {IREE_VM_VALUE_TYPE_NONE, IREE_VM_REF_TYPE_NULL}, {0}, }
-#define iree_vm_variant_is_value(v) iree_vm_type_def_is_value(&(v).type)
-#define iree_vm_variant_is_ref(v) iree_vm_type_def_is_ref(&(v).type)
-#define iree_vm_variant_is_empty(v) iree_vm_type_def_is_variant(&(v).type)
-
 #ifdef __cplusplus
 }  // extern "C"
 #endif  // __cplusplus
diff --git a/runtime/src/iree/vm/variant.h b/runtime/src/iree/vm/variant.h
new file mode 100644
index 0000000..dfff9ed
--- /dev/null
+++ b/runtime/src/iree/vm/variant.h
@@ -0,0 +1,103 @@
+// Copyright 2023 The IREE Authors
+//
+// Licensed under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef IREE_VM_VARIANT_H_
+#define IREE_VM_VARIANT_H_
+
+#include "iree/vm/ref.h"
+#include "iree/vm/value.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+// An variant value that can be either a primitive value type or a ref type.
+// Each variant value stores its type but users are required to check the type
+// prior to accessing any of the data.
+typedef struct iree_vm_variant_t {
+  iree_vm_type_def_t type;
+  union {
+    // TODO(benvanik): replace with iree_vm_value_t. Don't want to pay for 2x
+    // the type storage, though.
+    int8_t i8;
+    int16_t i16;
+    int32_t i32;
+    int64_t i64;
+    float f32;
+    double f64;
+    iree_vm_ref_t ref;
+    uint8_t value_storage[IREE_VM_VALUE_STORAGE_SIZE];  // max size of all value
+                                                        // types
+  };
+} iree_vm_variant_t;
+
+// Returns an empty variant.
+static inline iree_vm_variant_t iree_vm_variant_empty(void) {
+  iree_vm_variant_t result;
+  result.type = iree_vm_type_def_make_variant_type();
+  result.ref = iree_vm_ref_null();
+  return result;
+}
+
+// Returns true if |variant| is empty (no value/NULL ref).
+static inline bool iree_vm_variant_is_empty(iree_vm_variant_t variant) {
+  return iree_vm_type_def_is_variant(&variant.type);
+}
+
+// Returns true if |variant| represents a primitive value.
+static inline bool iree_vm_variant_is_value(iree_vm_variant_t variant) {
+  return iree_vm_type_def_is_value(&variant.type);
+}
+
+// Returns true if |variant| represents a non-NULL ref type.
+static inline bool iree_vm_variant_is_ref(iree_vm_variant_t variant) {
+  return iree_vm_type_def_is_ref(&variant.type);
+}
+
+// Makes a variant containing the given primitive |value|.
+static inline iree_vm_variant_t iree_vm_make_variant_value(
+    iree_vm_value_t value) {
+  iree_vm_variant_t result = iree_vm_variant_empty();
+  result.type.value_type = value.type;
+  memcpy(result.value_storage, value.value_storage,
+         sizeof(result.value_storage));
+  return result;
+}
+
+// Makes a variant containing the given |ref| type with assignment semantics.
+static inline iree_vm_variant_t iree_vm_make_variant_ref_assign(
+    iree_vm_ref_t ref) {
+  iree_vm_variant_t result = iree_vm_variant_empty();
+  result.type.ref_type = ref.type;
+  result.ref = ref;
+  return result;
+}
+
+// Returns the primitive value contained within |variant|, if any.
+// If the variant is not a value type the return will be the same as
+// iree_vm_value_make_none.
+static inline iree_vm_value_t iree_vm_variant_value(iree_vm_variant_t variant) {
+  iree_vm_value_t value;
+  value.type = variant.type.value_type;
+  memcpy(value.value_storage, variant.value_storage,
+         sizeof(value.value_storage));
+  return value;
+}
+
+// Resets |variant| to empty in-place and releases the contained ref, if set.
+static inline void iree_vm_variant_reset(iree_vm_variant_t* variant) {
+  if (!variant) return;
+  if (iree_vm_variant_is_ref(*variant)) {
+    iree_vm_ref_release(&variant->ref);
+  }
+  *variant = iree_vm_variant_empty();
+}
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif  // IREE_VM_VARIANT_H_
diff --git a/tools/iree-e2e-matmul-test.c b/tools/iree-e2e-matmul-test.c
index e77a89c..3615249 100644
--- a/tools/iree-e2e-matmul-test.c
+++ b/tools/iree-e2e-matmul-test.c
@@ -166,7 +166,7 @@
     iree_vm_list_t* list, iree_host_size_t i,
     iree_hal_buffer_view_t** out_value) {
   iree_vm_variant_t variant = iree_vm_variant_empty();
-  IREE_RETURN_IF_ERROR(iree_vm_list_get_variant(list, i, &variant));
+  IREE_RETURN_IF_ERROR(iree_vm_list_get_variant_assign(list, i, &variant));
   if (!iree_vm_variant_is_ref(variant)) {
     return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
                             "expected list item %zu to be a ref", i);