// 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_make_undefined_type_def();
  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_bits = 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_bits = ref.type >> IREE_VM_REF_TYPE_TAG_BITS;
  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 = (iree_vm_value_type_t)variant.type.value_type_bits;
  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_
