//===-- DescriptorModel.h -- model of descriptors for codegen ---*- C++ -*-===//
//
// Part of the LLVM Project, 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
//
//===----------------------------------------------------------------------===//
// LLVM IR dialect models of C++ types.
//
// This supplies a set of model builders to decompose the C declaration of a
// descriptor (as encoded in ISO_Fortran_binding.h and elsewhere) and
// reconstruct that type in the LLVM IR dialect.
//
// TODO: It is understood that this is deeply incorrect as far as building a
// portability layer for cross-compilation as these reflected types are those of
// the build machine and not necessarily that of either the host or the target.
// This assumption that build == host == target is actually pervasive across the
// compiler (https://llvm.org/PR52418).
//
//===----------------------------------------------------------------------===//

#ifndef OPTIMIZER_DESCRIPTOR_MODEL_H
#define OPTIMIZER_DESCRIPTOR_MODEL_H

#include "flang/ISO_Fortran_binding.h"
#include "flang/Runtime/descriptor.h"
#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "llvm/Support/ErrorHandling.h"
#include <tuple>

namespace fir {

using TypeBuilderFunc = mlir::Type (*)(mlir::MLIRContext *);

/// Get the LLVM IR dialect model for building a particular C++ type, `T`.
template <typename T>
TypeBuilderFunc getModel();

template <>
TypeBuilderFunc getModel<void *>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::LLVM::LLVMPointerType::get(mlir::IntegerType::get(context, 8));
  };
}
template <>
TypeBuilderFunc getModel<unsigned>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::IntegerType::get(context, sizeof(unsigned) * 8);
  };
}
template <>
TypeBuilderFunc getModel<int>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::IntegerType::get(context, sizeof(int) * 8);
  };
}
template <>
TypeBuilderFunc getModel<unsigned long>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::IntegerType::get(context, sizeof(unsigned long) * 8);
  };
}
template <>
TypeBuilderFunc getModel<unsigned long long>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::IntegerType::get(context, sizeof(unsigned long long) * 8);
  };
}
template <>
TypeBuilderFunc getModel<long long>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::IntegerType::get(context, sizeof(long long) * 8);
  };
}
template <>
TypeBuilderFunc getModel<Fortran::ISO::CFI_rank_t>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::IntegerType::get(context,
                                  sizeof(Fortran::ISO::CFI_rank_t) * 8);
  };
}
template <>
TypeBuilderFunc getModel<Fortran::ISO::CFI_type_t>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::IntegerType::get(context,
                                  sizeof(Fortran::ISO::CFI_type_t) * 8);
  };
}
template <>
TypeBuilderFunc getModel<long>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    return mlir::IntegerType::get(context, sizeof(long) * 8);
  };
}
template <>
TypeBuilderFunc getModel<Fortran::ISO::CFI_dim_t>() {
  return [](mlir::MLIRContext *context) -> mlir::Type {
    auto indexTy = getModel<Fortran::ISO::CFI_index_t>()(context);
    return mlir::LLVM::LLVMArrayType::get(indexTy, 3);
  };
}
template <>
TypeBuilderFunc
getModel<Fortran::ISO::cfi_internal::FlexibleArray<Fortran::ISO::CFI_dim_t>>() {
  return getModel<Fortran::ISO::CFI_dim_t>();
}

//===----------------------------------------------------------------------===//
// Descriptor reflection
//===----------------------------------------------------------------------===//

/// Get the type model of the field number `Field` in an ISO CFI descriptor.
template <int Field>
static constexpr TypeBuilderFunc getDescFieldTypeModel() {
  Fortran::ISO::Fortran_2018::CFI_cdesc_t dummyDesc{};
  // check that the descriptor is exactly 8 fields as specified in CFI_cdesc_t
  // in flang/include/flang/ISO_Fortran_binding.h.
  auto [a, b, c, d, e, f, g, h] = dummyDesc;
  auto tup = std::tie(a, b, c, d, e, f, g, h);
  auto field = std::get<Field>(tup);
  return getModel<decltype(field)>();
}

/// An extended descriptor is defined by a class in runtime/descriptor.h. The
/// three fields in the class are hard-coded here, unlike the reflection used on
/// the ISO parts, which are a POD.
template <int Field>
static constexpr TypeBuilderFunc getExtendedDescFieldTypeModel() {
  if constexpr (Field == 8) {
    return getModel<void *>();
  } else if constexpr (Field == 9) {
    return getModel<Fortran::runtime::typeInfo::TypeParameterValue>();
  } else {
    llvm_unreachable("extended ISO descriptor only has 10 fields");
  }
}

} // namespace fir

#endif // OPTIMIZER_DESCRIPTOR_MODEL_H
