/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*!
 * \file codegen_params.cc
 */
#ifdef TVM_LLVM_VERSION

#include "codegen_params.h"

#include <llvm/ADT/ArrayRef.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/Support/Casting.h>

#include <algorithm>
#include <type_traits>
#include <vector>

namespace tvm {
namespace codegen {

template <typename T, typename E = void>
struct LLVMConstantGetter {
  static llvm::Constant* getElement(llvm::Type* ty, T t);
};

template <typename T>
struct LLVMConstantGetter<
    T, std::enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value)>> {
  static llvm::Constant* getElement(llvm::Type* ty, T t) {
    return llvm::ConstantInt::getSigned(ty, t);
  }
};

template <typename T>
struct LLVMConstantGetter<
    T, std::enable_if_t<(std::is_integral<T>::value && !std::is_signed<T>::value)>> {
  static llvm::Constant* getElement(llvm::Type* ty, T t) { return llvm::ConstantInt::get(ty, t); }
};

template <typename T>
struct LLVMConstantGetter<T, std::enable_if_t<std::is_floating_point<T>::value>> {
  static llvm::Constant* getElement(llvm::Type* ty, T t) { return llvm::ConstantFP::get(ty, t); }
};

template <typename T, typename = std::enable_if_t<std::is_standard_layout<T>::value &&
                                                  std::is_trivial<T>::value>>
void BuildLLVMVector(llvm::Type* element_type, void* tensor_data, size_t num_elements,
                     std::vector<llvm::Constant*>* elements) {
  elements->resize(num_elements, nullptr);
  std::transform(static_cast<T*>(tensor_data), static_cast<T*>(tensor_data) + num_elements,
                 elements->begin(),
                 [&](T t) { return LLVMConstantGetter<T>::getElement(element_type, t); });
}

llvm::ConstantArray* TensorToLLVMArray(llvm::LLVMContext* ctx, ::tvm::runtime::Tensor arr) {
  llvm::Type* element_type = nullptr;

  auto arr_type = arr.DataType();
  TVM_FFI_ICHECK(arr.IsContiguous()) << "CodegenParams: only support contiguous arrays";
  TVM_FFI_ICHECK_EQ(arr->device.device_type, kDLCPU)
      << "CodegenParams: only support contiguous arrays";
  TVM_FFI_ICHECK_EQ(arr_type.lanes, 1)
      << "CodegenParams: only support generating 1-lane parameters; saw " << arr_type.lanes;

  auto shape = arr.Shape();
  int num_elements = 1;
  for (auto shape_elem : shape) {
    num_elements *= shape_elem;
  }

  std::vector<llvm::Constant*> elements;

  switch (arr_type.code) {
    case kDLInt:
      TVM_FFI_ICHECK(arr_type.bits == 8 || arr_type.bits == 16 || arr_type.bits == 32 ||
                     arr_type.bits == 64)
          << "CodegenParams: only support generating 8-, 16-, 32-, or 64-bit integer params; saw "
          << arr_type.bits << "-bit array";
      element_type = llvm::Type::getIntNTy(*ctx, arr_type.bits);

      switch (arr_type.bits) {
        case 8:
          BuildLLVMVector<int8_t>(element_type, arr->data, num_elements, &elements);
          break;
        case 16:
          BuildLLVMVector<int16_t>(element_type, arr->data, num_elements, &elements);
          break;
        case 32:
          BuildLLVMVector<int32_t>(element_type, arr->data, num_elements, &elements);
          break;
        case 64:
          BuildLLVMVector<int64_t>(element_type, arr->data, num_elements, &elements);
          break;
        default:
          TVM_FFI_ICHECK(false) << "should not get here";
          break;
      }
      break;

    case kDLUInt:
      TVM_FFI_ICHECK(arr_type.bits == 8 || arr_type.bits == 16 || arr_type.bits == 32 ||
                     arr_type.bits == 64)
          << "CodegenParams: only support generating 8-, 16-, 32-, or 64-bit integer params; saw "
          << arr_type.bits << "-bit array";
      element_type = llvm::Type::getIntNTy(*ctx, arr_type.bits);

      switch (arr_type.bits) {
        case 8:
          BuildLLVMVector<uint8_t>(element_type, arr->data, num_elements, &elements);
          break;
        case 16:
          BuildLLVMVector<uint16_t>(element_type, arr->data, num_elements, &elements);
          break;
        case 32:
          BuildLLVMVector<uint32_t>(element_type, arr->data, num_elements, &elements);
          break;
        case 64:
          BuildLLVMVector<uint64_t>(element_type, arr->data, num_elements, &elements);
          break;
        default:
          TVM_FFI_ICHECK(false) << "should not get here";
          break;
      }
      break;

    case kDLFloat:
      switch (arr_type.bits) {
        case 16:
          // NOTE: float16 is treated as uint16_t.
          element_type = llvm::Type::getIntNTy(*ctx, arr_type.bits);
          BuildLLVMVector<uint16_t>(element_type, arr->data, num_elements, &elements);
          break;
        case 32:
          element_type = llvm::Type::getFloatTy(*ctx);
          BuildLLVMVector<float>(element_type, arr->data, num_elements, &elements);
          break;
        case 64:
          element_type = llvm::Type::getDoubleTy(*ctx);
          BuildLLVMVector<double>(element_type, arr->data, num_elements, &elements);
          break;
        default:
          TVM_FFI_ICHECK(false) << "CodegenParams: only support 32- or 64-bit floating point; saw "
                                << arr_type.bits << "-bit array";
          break;
      }
      break;

    case kDLBfloat:
      TVM_FFI_ICHECK(arr_type.bits == 16)
          << "CodegenParams: only support 16-bit bfloat; saw " << arr_type.bits << "-bit array";
      element_type = llvm::Type::getIntNTy(*ctx, arr_type.bits);
      BuildLLVMVector<uint16_t>(element_type, arr->data, num_elements, &elements);

    default:
      TVM_FFI_ICHECK(false) << "Data type not supported";
  }

  return llvm::cast<llvm::ConstantArray>(llvm::ConstantArray::get(
      llvm::ArrayType::get(element_type, num_elements), llvm::ArrayRef<llvm::Constant*>(elements)));
}

}  // namespace codegen
}  // namespace tvm

#endif  // TVM_LLVM_VERSION
