// Copyright 2020 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "iree/vm/list.h"

#include <cstdint>
#include <cstring>

#include "iree/base/api.h"
#include "iree/testing/gtest.h"
#include "iree/testing/status_matchers.h"
#include "iree/vm/instance.h"
#include "iree/vm/ref.h"

class A : public iree::vm::RefObject<A> {
 public:
  float data() const { return data_; }
  void set_data(float value) { data_ = value; }

 private:
  float data_ = 1.0f;
};
IREE_VM_DECLARE_TYPE_ADAPTERS(test_a, A);
IREE_VM_DEFINE_TYPE_ADAPTERS(test_a, A);

class B : public iree::vm::RefObject<B> {
 public:
  int data() const { return data_; }
  void set_data(int value) { data_ = value; }

 private:
  int data_ = 2;
};
IREE_VM_DECLARE_TYPE_ADAPTERS(test_b, B);
IREE_VM_DEFINE_TYPE_ADAPTERS(test_b, B);

static bool operator==(const iree_vm_value_t& lhs,
                       const iree_vm_value_t& rhs) noexcept {
  if (lhs.type != rhs.type) return false;
  switch (lhs.type) {
    default:
    case IREE_VM_VALUE_TYPE_NONE:
      return true;  // none == none
    case IREE_VM_VALUE_TYPE_I8:
      return lhs.i8 == rhs.i8;
    case IREE_VM_VALUE_TYPE_I16:
      return lhs.i16 == rhs.i16;
    case IREE_VM_VALUE_TYPE_I32:
      return lhs.i32 == rhs.i32;
    case IREE_VM_VALUE_TYPE_I64:
      return lhs.i64 == rhs.i64;
    case IREE_VM_VALUE_TYPE_F32:
      return lhs.f32 == rhs.f32;
    case IREE_VM_VALUE_TYPE_F64:
      return lhs.f64 == rhs.f64;
  }
}

static std::ostream& operator<<(std::ostream& os,
                                const iree_vm_value_t& value) {
  switch (value.type) {
    default:
    case IREE_VM_VALUE_TYPE_NONE:
      return os << "??";
    case IREE_VM_VALUE_TYPE_I8:
      return os << value.i8;
    case IREE_VM_VALUE_TYPE_I16:
      return os << value.i16;
    case IREE_VM_VALUE_TYPE_I32:
      return os << value.i32;
    case IREE_VM_VALUE_TYPE_I64:
      return os << value.i64;
    case IREE_VM_VALUE_TYPE_F32:
      return os << value.f32;
    case IREE_VM_VALUE_TYPE_F64:
      return os << value.f64;
  }
}

template <size_t N>
static std::vector<iree_vm_value_t> MakeValuesList(const int32_t (&values)[N]) {
  std::vector<iree_vm_value_t> result;
  result.resize(N);
  for (size_t i = 0; i < N; ++i) result[i] = iree_vm_value_make_i32(values[i]);
  return result;
}

template <size_t N>
static std::vector<iree_vm_value_t> MakeValuesList(const float (&values)[N]) {
  std::vector<iree_vm_value_t> result;
  result.resize(N);
  for (size_t i = 0; i < N; ++i) result[i] = iree_vm_value_make_f32(values[i]);
  return result;
}

static std::vector<iree_vm_value_t> GetValuesList(iree_vm_list_t* list) {
  std::vector<iree_vm_value_t> result;
  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_assign(list, i, &variant));
    if (iree_vm_variant_is_value(variant)) {
      result[i] = iree_vm_variant_value(variant);
    } else if (iree_vm_variant_is_ref(variant)) {
      if (test_a_isa(variant.ref)) {
        result[i] = iree_vm_value_make_f32(test_a_deref(variant.ref)->data());
      } else if (test_b_isa(variant.ref)) {
        result[i] = iree_vm_value_make_i32(test_b_deref(variant.ref)->data());
      }
    }
  }
  return result;
}

static bool operator==(const iree_vm_ref_t& lhs,
                       const iree_vm_ref_t& rhs) noexcept {
  return lhs.type == rhs.type && lhs.ptr == rhs.ptr;
}

namespace {

using ::iree::Status;
using ::iree::StatusCode;
using ::iree::testing::status::StatusIs;
using testing::Eq;

template <typename T>
static void RegisterRefType(iree_vm_instance_t* instance, const char* type_name,
                            iree_vm_ref_type_t* out_registration) {
  static iree_vm_ref_type_descriptor_t storage = {0};
  storage.type_name = iree_make_cstring_view(type_name);
  storage.offsetof_counter = T::offsetof_counter();
  storage.destroy = T::DirectDestroy;
  IREE_CHECK_OK(
      iree_vm_instance_register_type(instance, &storage, out_registration));
}

static void RegisterRefTypes(iree_vm_instance_t* instance) {
  RegisterRefType<A>(instance, "AType", &test_a_registration);
  RegisterRefType<B>(instance, "BType", &test_b_registration);
}

template <typename T, typename V>
static iree_vm_ref_t MakeRef(V value) {
  iree_vm_ref_t ref = {0};
  auto* obj = new T();
  obj->set_data(value);
  IREE_CHECK_OK(iree_vm_ref_wrap_assign(
      obj, iree::vm::ref_type_descriptor<T>::type(), &ref));
  return ref;
}

static iree_vm_instance_t* instance = nullptr;
struct VMListTest : public ::testing::Test {
  static void SetUpTestSuite() {
    // Note: VM instance creation registers list types, which is required before
    // using the list APIs.
    IREE_CHECK_OK(iree_vm_instance_create(IREE_VM_TYPE_CAPACITY_DEFAULT,
                                          iree_allocator_system(), &instance));
    RegisterRefTypes(instance);
  }
  static void TearDownTestSuite() { iree_vm_instance_release(instance); }
};

// Tests simple primitive value list usage, mainly just for demonstration.
// Stores only i32 element types, equivalent to `!vm.list<i32>`.
TEST_F(VMListTest, UsageI32) {
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_host_size_t initial_capacity = 123;
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &list));

  iree_vm_type_def_t queried_element_type = iree_vm_list_element_type(list);
  EXPECT_TRUE(iree_vm_type_def_is_value(queried_element_type));
  EXPECT_TRUE(iree_vm_type_def_equal(element_type, queried_element_type));
  EXPECT_LE(initial_capacity, iree_vm_list_capacity(list));
  EXPECT_EQ(0, iree_vm_list_size(list));

  IREE_ASSERT_OK(iree_vm_list_resize(list, 5));
  EXPECT_EQ(5, iree_vm_list_size(list));

  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32((int32_t)i);
    IREE_ASSERT_OK(iree_vm_list_set_value(list, i, &value));
  }

  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value;
    IREE_ASSERT_OK(
        iree_vm_list_get_value_as(list, i, IREE_VM_VALUE_TYPE_I32, &value));
    EXPECT_EQ(IREE_VM_VALUE_TYPE_I32, value.type);
    EXPECT_EQ(i, value.i32);
  }

  iree_vm_list_release(list);
}

// Tests simple ref object list usage, mainly just for demonstration.
// Stores ref object type A elements only, equivalent to `!vm.list<!vm.ref<A>>`.
TEST_F(VMListTest, UsageRef) {
  iree_vm_type_def_t element_type = iree_vm_make_ref_type_def(test_a_type());
  iree_host_size_t initial_capacity = 123;
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &list));

  iree_vm_type_def_t queried_element_type = iree_vm_list_element_type(list);
  EXPECT_TRUE(iree_vm_type_def_is_ref(queried_element_type));
  EXPECT_TRUE(iree_vm_type_def_equal(element_type, queried_element_type));
  EXPECT_LE(initial_capacity, iree_vm_list_capacity(list));
  EXPECT_EQ(0, iree_vm_list_size(list));

  IREE_ASSERT_OK(iree_vm_list_resize(list, 5));
  EXPECT_EQ(5, iree_vm_list_size(list));

  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>((float)i);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(list, i, &ref_a));
  }

  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list, i, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }

  iree_vm_list_release(list);
}

TEST_F(VMListTest, GetRefRetainOrMove) {
  iree_vm_type_def_t element_type = iree_vm_make_ref_type_def(test_a_type());
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/1,
                                     iree_allocator_system(), &list));
  IREE_ASSERT_OK(iree_vm_list_resize(list, 1));

  // Retain path keeps the element intact.
  {
    iree_vm_ref_t ref_a = MakeRef<A>(1.0f);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(list, 0, &ref_a));
    iree_vm_ref_t retained{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain_or_move(
        list, 0, /*is_move=*/false, &retained));
    EXPECT_TRUE(test_a_isa(retained));
    iree_vm_ref_release(&retained);
    iree_vm_ref_t still_there{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_assign(list, 0, &still_there));
    EXPECT_TRUE(test_a_isa(still_there));
  }

  // Move path transfers ownership and clears the element.
  {
    iree_vm_ref_t ref_a = MakeRef<A>(2.0f);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(list, 0, &ref_a));
    iree_vm_ref_t moved{0};
    IREE_ASSERT_OK(
        iree_vm_list_get_ref_retain_or_move(list, 0, /*is_move=*/true, &moved));
    EXPECT_TRUE(test_a_isa(moved));
    iree_vm_ref_release(&moved);
    iree_vm_ref_t cleared{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_assign(list, 0, &cleared));
    EXPECT_EQ(IREE_VM_REF_TYPE_NULL, cleared.type);
  }

  iree_vm_list_release(list);
}

// Tests that moving a ref from a variant list properly marks the slot as empty.
TEST_F(VMListTest, VariantListRefMoveMarksSlotEmpty) {
  // Create a variant list (stores any type).
  iree_vm_type_def_t element_type = iree_vm_make_undefined_type_def();
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/1,
                                     iree_allocator_system(), &list));
  IREE_ASSERT_OK(iree_vm_list_resize(list, 1));

  // Set a ref into the variant slot.
  iree_vm_ref_t ref_a = MakeRef<A>(1.0f);
  IREE_ASSERT_OK(iree_vm_list_set_ref_move(list, 0, &ref_a));

  // Verify the slot contains a ref.
  {
    iree_vm_variant_t variant;
    IREE_ASSERT_OK(iree_vm_list_get_variant_assign(list, 0, &variant));
    EXPECT_TRUE(iree_vm_variant_is_ref(variant));
    EXPECT_FALSE(iree_vm_variant_is_empty(variant));
  }

  // Move the ref out of the variant list.
  iree_vm_ref_t moved{0};
  IREE_ASSERT_OK(
      iree_vm_list_get_ref_retain_or_move(list, 0, /*is_move=*/true, &moved));
  EXPECT_TRUE(test_a_isa(moved));
  iree_vm_ref_release(&moved);

  // Verify the slot is now empty (type should be undefined/variant).
  {
    iree_vm_variant_t variant;
    IREE_ASSERT_OK(iree_vm_list_get_variant_assign(list, 0, &variant));
    EXPECT_TRUE(iree_vm_variant_is_empty(variant))
        << "After move, variant slot should be empty";
  }

  // Also test get_variant_move marks the slot empty.
  {
    iree_vm_ref_t ref_b = MakeRef<A>(2.0f);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(list, 0, &ref_b));

    iree_vm_variant_t moved_variant;
    IREE_ASSERT_OK(iree_vm_list_get_variant_move(list, 0, &moved_variant));
    EXPECT_TRUE(iree_vm_variant_is_ref(moved_variant));
    iree_vm_ref_release(&moved_variant.ref);

    iree_vm_variant_t after_move;
    IREE_ASSERT_OK(iree_vm_list_get_variant_assign(list, 0, &after_move));
    EXPECT_TRUE(iree_vm_variant_is_empty(after_move))
        << "After get_variant_move, slot should be empty";
  }

  iree_vm_list_release(list);
}

// Tests simple variant list usage, mainly just for demonstration.
// Stores any heterogeneous element type, equivalent to `!vm.list<?>`.
TEST_F(VMListTest, UsageVariant) {
  iree_vm_type_def_t element_type = iree_vm_make_undefined_type_def();
  iree_host_size_t initial_capacity = 123;
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &list));

  iree_vm_type_def_t queried_element_type = iree_vm_list_element_type(list);
  EXPECT_TRUE(iree_vm_type_def_is_variant(queried_element_type));
  EXPECT_LE(initial_capacity, iree_vm_list_capacity(list));
  EXPECT_EQ(0, iree_vm_list_size(list));

  IREE_ASSERT_OK(iree_vm_list_resize(list, 10));
  EXPECT_EQ(10, iree_vm_list_size(list));

  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32((int32_t)i);
    IREE_ASSERT_OK(iree_vm_list_set_value(list, i, &value));
  }
  for (iree_host_size_t i = 5; i < 10; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>(static_cast<float>(i));
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(list, i, &ref_a));
  }

  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value;
    IREE_ASSERT_OK(
        iree_vm_list_get_value_as(list, i, IREE_VM_VALUE_TYPE_I32, &value));
    EXPECT_EQ(IREE_VM_VALUE_TYPE_I32, value.type);
    EXPECT_EQ(i, value.i32);
  }
  for (iree_host_size_t i = 5; i < 10; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list, i, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }

  iree_vm_list_release(list);
}

// Tests cloning lists of value types.
TEST_F(VMListTest, CloneValuesEmpty) {
  // Create source list.
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_host_size_t initial_capacity = 123;
  iree_vm_list_t* source_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &source_list));

  // Clone list.
  iree_vm_list_t* target_list = nullptr;
  IREE_ASSERT_OK(
      iree_vm_list_clone(source_list, iree_allocator_system(), &target_list));

  // Verify the target list matches source parameters.
  iree_vm_type_def_t queried_element_type =
      iree_vm_list_element_type(target_list);
  EXPECT_TRUE(iree_vm_type_def_is_value(queried_element_type));
  EXPECT_TRUE(iree_vm_type_def_equal(element_type, queried_element_type));
  EXPECT_LE(iree_vm_list_capacity(target_list),
            iree_vm_list_capacity(source_list));
  EXPECT_EQ(iree_vm_list_size(target_list), iree_vm_list_size(source_list));

  iree_vm_list_release(source_list);
  iree_vm_list_release(target_list);
}
TEST_F(VMListTest, CloneValues) {
  // Create source list.
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_host_size_t initial_capacity = 123;
  iree_vm_list_t* source_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &source_list));
  IREE_ASSERT_OK(iree_vm_list_resize(source_list, 5));
  EXPECT_EQ(5, iree_vm_list_size(source_list));
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32((int32_t)i);
    IREE_ASSERT_OK(iree_vm_list_set_value(source_list, i, &value));
  }

  // Clone list.
  iree_vm_list_t* target_list = nullptr;
  IREE_ASSERT_OK(
      iree_vm_list_clone(source_list, iree_allocator_system(), &target_list));

  // Verify the contents match.
  EXPECT_EQ(iree_vm_list_size(target_list), iree_vm_list_size(source_list));
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value;
    IREE_ASSERT_OK(iree_vm_list_get_value_as(target_list, i,
                                             IREE_VM_VALUE_TYPE_I32, &value));
    EXPECT_EQ(IREE_VM_VALUE_TYPE_I32, value.type);
    EXPECT_EQ(i, value.i32);
  }

  iree_vm_list_release(source_list);
  iree_vm_list_release(target_list);
}

// Tests cloning lists of ref types.
TEST_F(VMListTest, CloneRefsEmpty) {
  iree_vm_type_def_t element_type = iree_vm_make_ref_type_def(test_a_type());
  iree_vm_list_t* source_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, 8, iree_allocator_system(),
                                     &source_list));

  // Clone list.
  iree_vm_list_t* target_list = nullptr;
  IREE_ASSERT_OK(
      iree_vm_list_clone(source_list, iree_allocator_system(), &target_list));

  // Verify the target list matches source parameters.
  iree_vm_type_def_t queried_element_type =
      iree_vm_list_element_type(target_list);
  EXPECT_TRUE(iree_vm_type_def_is_ref(queried_element_type));
  EXPECT_TRUE(iree_vm_type_def_equal(element_type, queried_element_type));
  EXPECT_LE(iree_vm_list_capacity(target_list),
            iree_vm_list_capacity(source_list));
  EXPECT_EQ(iree_vm_list_size(target_list), iree_vm_list_size(source_list));

  iree_vm_list_release(source_list);
  iree_vm_list_release(target_list);
}
TEST_F(VMListTest, CloneRefs) {
  iree_vm_type_def_t element_type = iree_vm_make_ref_type_def(test_a_type());
  iree_vm_list_t* source_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, 8, iree_allocator_system(),
                                     &source_list));
  IREE_ASSERT_OK(iree_vm_list_resize(source_list, 5));
  EXPECT_EQ(5, iree_vm_list_size(source_list));
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>((float)i);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(source_list, i, &ref_a));
  }

  // Clone list.
  iree_vm_list_t* target_list = nullptr;
  IREE_ASSERT_OK(
      iree_vm_list_clone(source_list, iree_allocator_system(), &target_list));

  // Verify the contents match. Since they are refs we compare pointer equality
  // to ensure they were shallowly cloned.
  EXPECT_EQ(iree_vm_list_size(target_list), iree_vm_list_size(source_list));
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t source_ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_assign(source_list, i, &source_ref_a));
    EXPECT_TRUE(test_a_isa(source_ref_a));
    auto* source_a = test_a_deref(source_ref_a);
    iree_vm_ref_t target_ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_assign(target_list, i, &target_ref_a));
    EXPECT_TRUE(test_a_isa(target_ref_a));
    auto* target_a = test_a_deref(target_ref_a);
    EXPECT_EQ(source_a, target_a);
  }

  iree_vm_list_release(source_list);
  iree_vm_list_release(target_list);
}

// Tests cloning lists of variant types.
TEST_F(VMListTest, CloneVariantsEmpty) {
  iree_vm_type_def_t element_type = iree_vm_make_undefined_type_def();
  iree_vm_list_t* source_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, 10, iree_allocator_system(),
                                     &source_list));

  // Clone list.
  iree_vm_list_t* target_list = nullptr;
  IREE_ASSERT_OK(
      iree_vm_list_clone(source_list, iree_allocator_system(), &target_list));

  // Verify the target list matches source parameters.
  iree_vm_type_def_t queried_element_type =
      iree_vm_list_element_type(target_list);
  EXPECT_TRUE(iree_vm_type_def_is_variant(queried_element_type));
  EXPECT_TRUE(iree_vm_type_def_equal(element_type, queried_element_type));
  EXPECT_LE(iree_vm_list_capacity(target_list),
            iree_vm_list_capacity(source_list));
  EXPECT_EQ(iree_vm_list_size(target_list), iree_vm_list_size(source_list));

  iree_vm_list_release(source_list);
  iree_vm_list_release(target_list);
}
TEST_F(VMListTest, CloneVariants) {
  iree_vm_type_def_t element_type = iree_vm_make_undefined_type_def();
  iree_vm_list_t* source_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, 10, iree_allocator_system(),
                                     &source_list));
  IREE_ASSERT_OK(iree_vm_list_resize(source_list, 10));
  EXPECT_EQ(10, iree_vm_list_size(source_list));
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32((int32_t)i);
    IREE_ASSERT_OK(iree_vm_list_set_value(source_list, i, &value));
  }
  for (iree_host_size_t i = 5; i < 10; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>(static_cast<float>(i));
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(source_list, i, &ref_a));
  }

  // Clone list.
  iree_vm_list_t* target_list = nullptr;
  IREE_ASSERT_OK(
      iree_vm_list_clone(source_list, iree_allocator_system(), &target_list));

  // Verify the contents match. Since they are refs we compare pointer equality
  // to ensure they were shallowly cloned.
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value;
    IREE_ASSERT_OK(iree_vm_list_get_value_as(target_list, i,
                                             IREE_VM_VALUE_TYPE_I32, &value));
    EXPECT_EQ(IREE_VM_VALUE_TYPE_I32, value.type);
    EXPECT_EQ(i, value.i32);
  }
  for (iree_host_size_t i = 5; i < 10; ++i) {
    iree_vm_ref_t source_ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_assign(source_list, i, &source_ref_a));
    EXPECT_TRUE(test_a_isa(source_ref_a));
    auto* source_a = test_a_deref(source_ref_a);
    iree_vm_ref_t target_ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_assign(target_list, i, &target_ref_a));
    EXPECT_TRUE(test_a_isa(target_ref_a));
    auto* target_a = test_a_deref(target_ref_a);
    EXPECT_EQ(source_a, target_a);
  }

  iree_vm_list_release(source_list);
  iree_vm_list_release(target_list);
}

// Tests capacity reservation.
TEST_F(VMListTest, Reserve) {
  // Allocate with 0 initial capacity (which may get rounded up).
  iree_vm_type_def_t element_type = iree_vm_make_undefined_type_def();
  iree_host_size_t initial_capacity = 0;
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &list));
  EXPECT_LE(initial_capacity, iree_vm_list_capacity(list));
  EXPECT_EQ(0, iree_vm_list_size(list));

  // Reserve some capacity, which may allocate.
  IREE_ASSERT_OK(iree_vm_list_reserve(list, 100));
  iree_host_size_t current_capacity = iree_vm_list_capacity(list);
  EXPECT_LE(100, current_capacity);

  // Resize to add items, which should not change capacity.
  IREE_ASSERT_OK(iree_vm_list_resize(list, 1));
  EXPECT_EQ(1, iree_vm_list_size(list));
  EXPECT_EQ(current_capacity, iree_vm_list_capacity(list));

  // Reserving <= the current capacity should be a no-op.
  IREE_ASSERT_OK(iree_vm_list_reserve(list, current_capacity));
  EXPECT_EQ(current_capacity, iree_vm_list_capacity(list));

  iree_vm_list_release(list);
}

// Tests that reserving extremely large capacities fails gracefully.
// This verifies the overflow checking in reserve().
TEST_F(VMListTest, ReserveOverflow) {
  iree_vm_type_def_t element_type = iree_vm_make_undefined_type_def();
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/0,
                                     iree_allocator_system(), &list));

  // Request a capacity that would overflow when aligned to 64-element boundary.
  // IREE_HOST_SIZE_MAX - 32 when aligned up to 64 would overflow.
  iree_host_size_t overflow_capacity = IREE_HOST_SIZE_MAX - 32;
  EXPECT_THAT(Status(iree_vm_list_reserve(list, overflow_capacity)),
              StatusIs(StatusCode::kOutOfRange));

  // List should still be usable after failed reserve.
  EXPECT_EQ(0, iree_vm_list_size(list));
  IREE_ASSERT_OK(iree_vm_list_resize(list, 10));
  EXPECT_EQ(10, iree_vm_list_size(list));

  iree_vm_list_release(list);
}

// Tests that resizing to extremely large sizes fails gracefully.
TEST_F(VMListTest, ResizeOverflow) {
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/4,
                                     iree_allocator_system(), &list));

  // Request a size that would overflow when computing storage requirements.
  iree_host_size_t overflow_size = IREE_HOST_SIZE_MAX / 2;
  EXPECT_THAT(Status(iree_vm_list_resize(list, overflow_size)),
              StatusIs(StatusCode::kOutOfRange));

  // List should still be usable after failed resize.
  EXPECT_EQ(0, iree_vm_list_size(list));
  IREE_ASSERT_OK(iree_vm_list_resize(list, 10));
  EXPECT_EQ(10, iree_vm_list_size(list));

  iree_vm_list_release(list);
}

// Tests the behavior of resize for truncation and extension on primitives.
TEST_F(VMListTest, ResizeI32) {
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_host_size_t initial_capacity = 4;
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &list));
  EXPECT_LE(initial_capacity, iree_vm_list_capacity(list));
  EXPECT_EQ(0, iree_vm_list_size(list));

  // Extend and zero-initialize.
  IREE_ASSERT_OK(iree_vm_list_resize(list, 5));
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value;
    IREE_ASSERT_OK(
        iree_vm_list_get_value_as(list, i, IREE_VM_VALUE_TYPE_I32, &value));
    EXPECT_EQ(0, value.i32);
  }

  // Overwrite with [0, 5).
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32((int32_t)i);
    IREE_ASSERT_OK(iree_vm_list_set_value(list, i, &value));
  }

  // Truncate to [0, 2) and then extend again.
  // This ensures that we test the primitive clearing path during cleanup:
  // [int, int, int, int, int]
  //            |___________| <- truncation region
  IREE_ASSERT_OK(iree_vm_list_resize(list, 2));
  IREE_ASSERT_OK(iree_vm_list_resize(list, 5));

  // Ensure that elements 2+ are zeroed after having been reset while 0 and 1
  // are still valid as before.
  for (iree_host_size_t i = 0; i < 2; ++i) {
    iree_vm_value_t value;
    IREE_ASSERT_OK(
        iree_vm_list_get_value_as(list, i, IREE_VM_VALUE_TYPE_I32, &value));
    EXPECT_EQ(i, value.i32);
  }
  for (iree_host_size_t i = 2; i < 5; ++i) {
    iree_vm_value_t value;
    IREE_ASSERT_OK(
        iree_vm_list_get_value_as(list, i, IREE_VM_VALUE_TYPE_I32, &value));
    EXPECT_EQ(0, value.i32);
  }

  iree_vm_list_release(list);
}

// Tests the behavior of resize for truncation and extension on refs.
TEST_F(VMListTest, ResizeRef) {
  iree_vm_type_def_t element_type = iree_vm_make_ref_type_def(test_a_type());
  iree_host_size_t initial_capacity = 4;
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &list));
  EXPECT_LE(initial_capacity, iree_vm_list_capacity(list));
  EXPECT_EQ(0, iree_vm_list_size(list));

  // Extend and zero-initialize.
  IREE_ASSERT_OK(iree_vm_list_resize(list, 5));
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_assign(list, i, &ref_a));
    EXPECT_TRUE(iree_vm_ref_is_null(&ref_a));
  }

  // Overwrite with [0, 5).
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>((float)i);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(list, i, &ref_a));
  }

  // Truncate to [0, 2) and then extend again.
  // This ensures that we test the ref path during cleanup:
  // [ref, ref, ref, ref, ref]
  //            |___________| <- truncation region
  IREE_ASSERT_OK(iree_vm_list_resize(list, 2));
  IREE_ASSERT_OK(iree_vm_list_resize(list, 5));

  // Ensure that elements 2+ are reset after having been reset while 0 and 1
  // are still valid as before.
  for (iree_host_size_t i = 0; i < 2; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list, i, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }
  for (iree_host_size_t i = 2; i < 5; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_assign(list, i, &ref_a));
    EXPECT_TRUE(iree_vm_ref_is_null(&ref_a));
  }

  iree_vm_list_release(list);
}

// Tests the behavior of resize for truncation and extension on variants.
TEST_F(VMListTest, ResizeVariant) {
  iree_vm_type_def_t element_type = iree_vm_make_undefined_type_def();
  iree_host_size_t initial_capacity = 4;
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &list));
  EXPECT_LE(initial_capacity, iree_vm_list_capacity(list));
  EXPECT_EQ(0, iree_vm_list_size(list));

  // Extend and zero-initialize.
  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_assign(list, i, &value));
    EXPECT_TRUE(iree_vm_variant_is_empty(value));
  }

  // Overwrite with [0, 5) in mixed types.
  for (iree_host_size_t i = 0; i < 4; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>((float)i);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(list, i, &ref_a));
  }
  for (iree_host_size_t i = 4; i < 5; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32((int32_t)i);
    IREE_ASSERT_OK(iree_vm_list_set_value(list, i, &value));
  }

  // Truncate to [0, 2) and then extend again.
  // This ensures that we test the variant path during cleanup:
  // [ref, ref, ref, ref, int]
  //            |___________| <- truncation region
  IREE_ASSERT_OK(iree_vm_list_resize(list, 2));
  IREE_ASSERT_OK(iree_vm_list_resize(list, 5));

  // Ensure that elements 2+ are reset after having been reset while 0 and 1
  // are still valid as before.
  for (iree_host_size_t i = 0; i < 2; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list, i, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }
  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_assign(list, i, &value));
    EXPECT_TRUE(iree_vm_variant_is_empty(value));
  }

  iree_vm_list_release(list);
}

// Tests that swapping the storage of a list with itself is a no-op.
TEST_F(VMListTest, SwapStorageSelf) {
  iree_vm_type_def_t element_type = iree_vm_make_ref_type_def(test_a_type());
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &list));
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>((float)i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(list, &ref_a));
  }

  iree_vm_list_swap_storage(list, list);

  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list, i, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }

  iree_vm_list_release(list);
}

// Tests swapping the storage of two lists with different types. The lists
// should have their types, size, and storage swapped.
TEST_F(VMListTest, SwapStorage) {
  iree_vm_type_def_t element_type_a = iree_vm_make_ref_type_def(test_a_type());
  iree_vm_list_t* list_a = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type_a, /*initial_capacity=*/8,
                                     iree_allocator_system(), &list_a));
  iree_host_size_t list_a_size = 4;
  for (iree_host_size_t i = 0; i < list_a_size; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>((float)i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(list_a, &ref_a));
  }

  iree_vm_type_def_t element_type_b = iree_vm_make_ref_type_def(test_b_type());
  iree_vm_list_t* list_b = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type_b, /*initial_capacity=*/8,
                                     iree_allocator_system(), &list_b));
  iree_host_size_t list_b_size = 3;
  for (iree_host_size_t i = 0; i < list_b_size; ++i) {
    iree_vm_ref_t ref_b = MakeRef<B>((float)i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(list_b, &ref_b));
  }

  iree_vm_list_swap_storage(list_a, list_b);

  // list_a should have b types.
  iree_vm_type_def_t queried_type_a = iree_vm_list_element_type(list_a);
  EXPECT_TRUE(iree_vm_type_def_equal(queried_type_a, element_type_b));
  EXPECT_EQ(iree_vm_list_size(list_a), list_b_size);
  for (iree_host_size_t i = 0; i < list_b_size; ++i) {
    iree_vm_ref_t ref_b{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list_a, i, &ref_b));
    EXPECT_TRUE(test_b_isa(ref_b));
    auto* b = test_b_deref(ref_b);
    EXPECT_EQ(i, b->data());
    iree_vm_ref_release(&ref_b);
  }

  // list_b should have a types.
  iree_vm_type_def_t queried_type_b = iree_vm_list_element_type(list_b);
  EXPECT_TRUE(iree_vm_type_def_equal(queried_type_b, element_type_a));
  EXPECT_EQ(iree_vm_list_size(list_b), list_a_size);
  for (iree_host_size_t i = 0; i < list_b_size; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list_b, i, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }

  iree_vm_list_release(list_a);
  iree_vm_list_release(list_b);
}

// Tests the boundary conditions around out-of-range copies.
// All of the logic for this is shared across all the various copy modes.
TEST_F(VMListTest, CopyOutOfRange) {
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_vm_list_t* src_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &src_list));
  iree_vm_list_t* dst_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &dst_list));

  // Lists are both empty - everything should fail.
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 0));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 0));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 100, 1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 100)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 5)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 3, dst_list, 0, 1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 3, 1)),
              StatusIs(StatusCode::kOutOfRange));

  // Source has valid ranges, destination does not.
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 4));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 0));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 100, 1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 100)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 5)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 3, dst_list, 0, 1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 3, 1)),
              StatusIs(StatusCode::kOutOfRange));

  // Destination has valid ranges, source does not.
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 0));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 4));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 100, 1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 100)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 5)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 3, dst_list, 0, 1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 3, 1)),
              StatusIs(StatusCode::kOutOfRange));

  // Mismatches.
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 4));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 4));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 100, 1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 100)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 5)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 4, dst_list, 0, 1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 4, 1)),
              StatusIs(StatusCode::kOutOfRange));

  iree_vm_list_release(src_list);
  iree_vm_list_release(dst_list);
}

// Tests copying values between lists.
TEST_F(VMListTest, CopyValues) {
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);

  // src: [0, 1, 2, 3]
  iree_vm_list_t* src_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &src_list));
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 4));
  for (iree_host_size_t i = 0; i < 4; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(i);
    IREE_ASSERT_OK(iree_vm_list_set_value(src_list, i, &value));
  }

  // dst: [4, 5, 6, 7]
  iree_vm_list_t* dst_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &dst_list));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 4));
  for (iree_host_size_t i = 0; i < 4; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(4 + i);
    IREE_ASSERT_OK(iree_vm_list_set_value(dst_list, i, &value));
  }

  // Copy no items (no-op).
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 0));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({4, 5, 6, 7})));

  // Copy at start.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 7})));

  // Copy at end.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 3, dst_list, 3, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 3})));

  // Copy a range in the middle.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 1, dst_list, 1, 2));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 1, 2, 3})));

  // Scatter.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 1, 3));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 0, 1, 2})));

  // Try to copy over source - this should fail as we don't support memmove.
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, src_list, 1, 2)),
              StatusIs(StatusCode::kInvalidArgument));

  // But copying over non-overlapping source ranges should be ok.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, src_list, 2, 2));
  EXPECT_THAT(GetValuesList(src_list), Eq(MakeValuesList({0, 1, 0, 1})));

  iree_vm_list_release(src_list);
  iree_vm_list_release(dst_list);
}

// Tests that trying to copy between values of different types will fail.
// Note that we use sizeof(int) == sizeof(float) as even though the sizes match
// we still want to fail (bitcasting would be bad).
TEST_F(VMListTest, CopyWrongValues) {
  // src: [0, 1, 2, 3]
  iree_vm_type_def_t src_element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_vm_list_t* src_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(src_element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &src_list));
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 4));
  for (iree_host_size_t i = 0; i < 4; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(i);
    IREE_ASSERT_OK(iree_vm_list_set_value(src_list, i, &value));
  }

  // dst: [4.0, 5.0, 6.0, 7.0]
  iree_vm_type_def_t dst_element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_F32);
  iree_vm_list_t* dst_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(dst_element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &dst_list));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 4));
  for (iree_host_size_t i = 0; i < 4; ++i) {
    iree_vm_value_t value = iree_vm_value_make_f32(4 + i);
    IREE_ASSERT_OK(iree_vm_list_set_value(dst_list, i, &value));
  }

  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 1)),
              StatusIs(StatusCode::kInvalidArgument));

  iree_vm_list_release(src_list);
  iree_vm_list_release(dst_list);
}

// Tests copying refs between lists of !vm.ref<?>.
TEST_F(VMListTest, CopyRefs) {
  iree_vm_type_def_t element_type =
      iree_vm_make_ref_type_def(IREE_VM_REF_TYPE_ANY);

  // src: [0, 1, 2, 3]
  iree_vm_list_t* src_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &src_list));
  iree_host_size_t src_list_size = 4;
  for (iree_host_size_t i = 0; i < src_list_size; ++i) {
    iree_vm_ref_t ref = MakeRef<B>(i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(src_list, &ref));
  }

  // dst: [4, 5, 6, 7]
  iree_vm_list_t* dst_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &dst_list));
  iree_host_size_t dst_list_size = 4;
  for (iree_host_size_t i = 0; i < dst_list_size; ++i) {
    iree_vm_ref_t ref = MakeRef<B>(4 + i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(dst_list, &ref));
  }

  // Copy no items (no-op).
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 0));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({4, 5, 6, 7})));

  // Copy at start.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 7})));

  // Copy at end.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 3, dst_list, 3, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 3})));

  // Copy a range in the middle.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 1, dst_list, 1, 2));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 1, 2, 3})));

  // Scatter.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 1, 3));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 0, 1, 2})));

  // Try to copy over source - this should fail as we don't support memmove.
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, src_list, 1, 2)),
              StatusIs(StatusCode::kInvalidArgument));

  // But copying over non-overlapping source ranges should be ok.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, src_list, 2, 2));
  EXPECT_THAT(GetValuesList(src_list), Eq(MakeValuesList({0, 1, 0, 1})));

  iree_vm_list_release(src_list);
  iree_vm_list_release(dst_list);
}

// Tests that trying to copy between refs of different types will fail.
TEST_F(VMListTest, CopyWrongRefs) {
  // src: type A
  iree_vm_type_def_t src_element_type =
      iree_vm_make_ref_type_def(test_a_type());
  iree_vm_list_t* src_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(src_element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &src_list));
  iree_host_size_t src_list_size = 4;
  for (iree_host_size_t i = 0; i < src_list_size; ++i) {
    iree_vm_ref_t ref = MakeRef<A>(i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(src_list, &ref));
  }

  // dst: type B
  iree_vm_type_def_t dst_element_type =
      iree_vm_make_ref_type_def(test_b_type());
  iree_vm_list_t* dst_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(dst_element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &dst_list));
  iree_host_size_t dst_list_size = 4;
  for (iree_host_size_t i = 0; i < dst_list_size; ++i) {
    iree_vm_ref_t ref = MakeRef<B>(4 + i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(dst_list, &ref));
  }

  // Copy no items (no-op). Should be ok.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 0));

  // Copies should fail because the types don't match.
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, dst_list, 0, 1)),
              StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(Status(iree_vm_list_copy(dst_list, 0, src_list, 0, 1)),
              StatusIs(StatusCode::kInvalidArgument));

  iree_vm_list_release(src_list);
  iree_vm_list_release(dst_list);
}

// Tests copying between variant lists.
TEST_F(VMListTest, CopyVariants) {
  // src: [0, 1, B(2), B(3)]
  iree_vm_list_t* src_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(iree_vm_make_undefined_type_def(),
                                     /*initial_capacity=*/8,
                                     iree_allocator_system(), &src_list));
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 4));
  for (iree_host_size_t i = 0; i < 2; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(i);
    IREE_ASSERT_OK(iree_vm_list_set_value(src_list, i, &value));
  }
  for (iree_host_size_t i = 2; i < 4; ++i) {
    iree_vm_ref_t ref = MakeRef<B>(i);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(src_list, i, &ref));
  }

  // dst: [4, 5, B(6), B(7)]
  iree_vm_list_t* dst_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(iree_vm_make_undefined_type_def(),
                                     /*initial_capacity=*/8,
                                     iree_allocator_system(), &dst_list));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 4));
  for (iree_host_size_t i = 0; i < 2; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(4 + i);
    IREE_ASSERT_OK(iree_vm_list_set_value(dst_list, i, &value));
  }
  for (iree_host_size_t i = 2; i < 4; ++i) {
    iree_vm_ref_t ref = MakeRef<B>(4 + i);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(dst_list, i, &ref));
  }

  // Copy no items (no-op).
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 0));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({4, 5, 6, 7})));

  // Copy at start.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 7})));

  // Copy at end.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 3, dst_list, 3, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 3})));

  // Copy a range in the middle.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 1, dst_list, 1, 2));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 1, 2, 3})));

  // Scatter.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 1, 3));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 0, 1, 2})));

  // Try to copy over source - this should fail as we don't support memmove.
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, src_list, 1, 2)),
              StatusIs(StatusCode::kInvalidArgument));

  // But copying over non-overlapping source ranges should be ok.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, src_list, 2, 2));
  EXPECT_THAT(GetValuesList(src_list), Eq(MakeValuesList({0, 1, 0, 1})));

  iree_vm_list_release(src_list);
  iree_vm_list_release(dst_list);
}

// Tests copying from variant lists to typed lists.
TEST_F(VMListTest, CopyFromVariants) {
  // src: [0, 1, B(2), B(3)]
  iree_vm_list_t* src_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(iree_vm_make_undefined_type_def(),
                                     /*initial_capacity=*/8,
                                     iree_allocator_system(), &src_list));
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 4));
  for (iree_host_size_t i = 0; i < 2; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(i);
    IREE_ASSERT_OK(iree_vm_list_set_value(src_list, i, &value));
  }
  for (iree_host_size_t i = 2; i < 4; ++i) {
    iree_vm_ref_t ref = MakeRef<B>(i);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(src_list, i, &ref));
  }

  // dst: [4, 5, 6, 7]
  iree_vm_type_def_t dst_element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_vm_list_t* dst_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(dst_element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &dst_list));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 4));
  for (iree_host_size_t i = 0; i < 4; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(4 + i);
    IREE_ASSERT_OK(iree_vm_list_set_value(dst_list, i, &value));
  }

  // Copy no items (no-op).
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 0));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({4, 5, 6, 7})));

  // Copy at start.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 7})));

  // Copy at end; should fail because the elements are ref types.
  // We check that nothing in the list changed.
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 3, dst_list, 3, 1)),
              StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 7})));

  // Copy a range in the middle including wrong types.
  // We check that nothing in the list changed.
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 1, dst_list, 1, 2)),
              StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 7})));

  // Scatter.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 1, 2));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 0, 1, 7})));

  // Try to copy over source - this should fail as we don't support memmove.
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, src_list, 1, 2)),
              StatusIs(StatusCode::kInvalidArgument));

  // But copying over non-overlapping source ranges should be ok.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, src_list, 2, 2));
  EXPECT_THAT(GetValuesList(src_list), Eq(MakeValuesList({0, 1, 0, 1})));

  iree_vm_list_release(src_list);
  iree_vm_list_release(dst_list);
}

// Tests copying from typed lists to variant lists.
TEST_F(VMListTest, CopyToVariants) {
  // src: [0, 1, 2, 3]
  iree_vm_type_def_t src_element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_I32);
  iree_vm_list_t* src_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(src_element_type, /*initial_capacity=*/8,
                                     iree_allocator_system(), &src_list));
  IREE_ASSERT_OK(iree_vm_list_resize(src_list, 4));
  for (iree_host_size_t i = 0; i < 4; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(i);
    IREE_ASSERT_OK(iree_vm_list_set_value(src_list, i, &value));
  }

  // dst: [4, 5, B(6), B(7)]
  iree_vm_list_t* dst_list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(iree_vm_make_undefined_type_def(),
                                     /*initial_capacity=*/8,
                                     iree_allocator_system(), &dst_list));
  IREE_ASSERT_OK(iree_vm_list_resize(dst_list, 4));
  for (iree_host_size_t i = 0; i < 2; ++i) {
    iree_vm_value_t value = iree_vm_value_make_i32(4 + i);
    IREE_ASSERT_OK(iree_vm_list_set_value(dst_list, i, &value));
  }
  for (iree_host_size_t i = 2; i < 4; ++i) {
    iree_vm_ref_t ref = MakeRef<B>(4 + i);
    IREE_ASSERT_OK(iree_vm_list_set_ref_move(dst_list, i, &ref));
  }

  // Copy no items (no-op).
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 0));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({4, 5, 6, 7})));

  // Copy at start.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 0, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 7})));

  // Copy at end.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 3, dst_list, 3, 1));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 5, 6, 3})));

  // Copy a range in the middle.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 1, dst_list, 1, 2));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 1, 2, 3})));

  // Scatter.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, dst_list, 1, 3));
  EXPECT_THAT(GetValuesList(dst_list), Eq(MakeValuesList({0, 0, 1, 2})));

  // Try to copy over source - this should fail as we don't support memmove.
  EXPECT_THAT(Status(iree_vm_list_copy(src_list, 0, src_list, 1, 2)),
              StatusIs(StatusCode::kInvalidArgument));

  // But copying over non-overlapping source ranges should be ok.
  IREE_EXPECT_OK(iree_vm_list_copy(src_list, 0, src_list, 2, 2));
  EXPECT_THAT(GetValuesList(src_list), Eq(MakeValuesList({0, 1, 0, 1})));

  iree_vm_list_release(src_list);
  iree_vm_list_release(dst_list);
}

// TODO(benvanik): test value get/set.

// TODO(benvanik): test value conversion.

// TODO(benvanik): test ref get/set.

// Tests pushing and popping ref objects.
TEST_F(VMListTest, PushPopRef) {
  iree_vm_type_def_t element_type = iree_vm_make_ref_type_def(test_a_type());
  iree_host_size_t initial_capacity = 4;
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, initial_capacity,
                                     iree_allocator_system(), &list));
  EXPECT_LE(initial_capacity, iree_vm_list_capacity(list));
  EXPECT_EQ(0, iree_vm_list_size(list));

  // Pops when empty fail.
  iree_vm_ref_t empty_ref{0};
  EXPECT_THAT(Status(iree_vm_list_pop_front_ref_move(list, &empty_ref)),
              StatusIs(StatusCode::kOutOfRange));

  // Push back [0, 5).
  for (iree_host_size_t i = 0; i < 5; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>((float)i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(list, &ref_a));
  }

  // Pop the first two [0, 1] and leave [2, 5).
  // This ensures that we test the ref path during cleanup:
  // [ref, ref, ref, ref, ref]
  //  |______| <- popped region
  for (iree_host_size_t i = 0; i < 2; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_pop_front_ref_move(list, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }

  // Ensure that elements 2+ are valid but now at offset 0.
  for (iree_host_size_t i = 2; i < 5; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list, i - 2, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }

  // Push back two more to get [2, 7).
  for (iree_host_size_t i = 5; i < 7; ++i) {
    iree_vm_ref_t ref_a = MakeRef<A>((float)i);
    IREE_ASSERT_OK(iree_vm_list_push_ref_move(list, &ref_a));
  }

  // Ensure the new elements got added to the end.
  for (iree_host_size_t i = 2; i < 7; ++i) {
    iree_vm_ref_t ref_a{0};
    IREE_ASSERT_OK(iree_vm_list_get_ref_retain(list, i - 2, &ref_a));
    EXPECT_TRUE(test_a_isa(ref_a));
    auto* a = test_a_deref(ref_a);
    EXPECT_EQ(i, a->data());
    iree_vm_ref_release(&ref_a);
  }

  iree_vm_list_release(list);
}

// TODO(benvanik): test primitive variant get/set.

// TODO(benvanik): test ref variant get/set.

// Test that F64 values (eg Python floats) can be converted to F32 values
// when pushed to a VM list expecting F32 values.
TEST_F(VMListTest, F64ToF32Conversion) {
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_F32);
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*capacity=*/10,
                                     iree_allocator_system(), &list));

  // Python passes floats as F64 (double)
  double python_value = 0.7;
  iree_vm_value_t f64_value = iree_vm_value_make_f64(python_value);

  // Push the F64 value to a list expecting F32
  // This internally calls iree_vm_list_convert_value_type(F64 -> F32)
  IREE_ASSERT_OK(iree_vm_list_push_value(list, &f64_value));

  // Retrieve the value and verify it was converted correctly
  iree_vm_value_t retrieved_value;
  IREE_ASSERT_OK(iree_vm_list_get_value(list, 0, &retrieved_value));
  EXPECT_EQ(retrieved_value.type, IREE_VM_VALUE_TYPE_F32);
  float expected_f32 = (float)python_value;
  EXPECT_NEAR(retrieved_value.f32, expected_f32, 1e-6f);
  EXPECT_NE(retrieved_value.f32, 0.0f);

  iree_vm_list_release(list);
}

// Test F32 to F64 conversion
TEST_F(VMListTest, F32ToF64Conversion) {
  iree_vm_type_def_t element_type =
      iree_vm_make_value_type_def(IREE_VM_VALUE_TYPE_F64);
  iree_vm_list_t* list = nullptr;
  IREE_ASSERT_OK(iree_vm_list_create(element_type, /*capacity=*/10,
                                     iree_allocator_system(), &list));

  // Push an F32 value to a list expecting F64
  float f32_value = 3.14159f;
  iree_vm_value_t value = iree_vm_value_make_f32(f32_value);
  IREE_ASSERT_OK(iree_vm_list_push_value(list, &value));

  // Retrieve and verify
  iree_vm_value_t retrieved_value;
  IREE_ASSERT_OK(iree_vm_list_get_value(list, 0, &retrieved_value));
  EXPECT_EQ(retrieved_value.type, IREE_VM_VALUE_TYPE_F64);
  EXPECT_NEAR(retrieved_value.f64, (double)f32_value, 1e-6);
  EXPECT_NE(retrieved_value.f64, 0.0);

  iree_vm_list_release(list);
}

}  // namespace
