//===- llvm/SandboxIR/Type.h - Classes for handling data types --*- 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
//
//===----------------------------------------------------------------------===//
//
// This is a thin wrapper over llvm::Type.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_SANDBOXIR_TYPE_H
#define LLVM_SANDBOXIR_TYPE_H

#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"

namespace llvm::sandboxir {

class Context;
// Forward declare friend classes for MSVC.
class PointerType;
class VectorType;
class FixedVectorType;
class ScalableVectorType;
class IntegerType;
class FunctionType;
class ArrayType;
class StructType;
class TargetExtType;
class Module;
class FPMathOperator;
#define DEF_INSTR(ID, OPCODE, CLASS) class CLASS;
#define DEF_CONST(ID, CLASS) class CLASS;
#include "llvm/SandboxIR/Values.def"

/// Just like llvm::Type these are immutable, unique, never get freed and
/// can only be created via static factory methods.
class Type {
protected:
  llvm::Type *LLVMTy;
  friend class ArrayType;          // For LLVMTy.
  friend class StructType;         // For LLVMTy.
  friend class VectorType;         // For LLVMTy.
  friend class FixedVectorType;    // For LLVMTy.
  friend class ScalableVectorType; // For LLVMTy.
  friend class PointerType;        // For LLVMTy.
  friend class FunctionType;       // For LLVMTy.
  friend class IntegerType;        // For LLVMTy.
  friend class Function;           // For LLVMTy.
  friend class CallBase;           // For LLVMTy.
  friend class ConstantInt;        // For LLVMTy.
  friend class ConstantArray;      // For LLVMTy.
  friend class ConstantStruct;     // For LLVMTy.
  friend class ConstantVector;     // For LLVMTy.
  friend class CmpInst;            // For LLVMTy. TODO: Cleanup after
                                   // sandboxir::VectorType is more complete.
  friend class Utils;              // for LLVMTy
  friend class TargetExtType;      // For LLVMTy.
  friend class Module;             // For LLVMTy.
  friend class FPMathOperator;     // For LLVMTy.

  // Friend all instruction classes because `create()` functions use LLVMTy.
#define DEF_INSTR(ID, OPCODE, CLASS) friend class CLASS;
#define DEF_CONST(ID, CLASS) friend class CLASS;
#include "llvm/SandboxIR/Values.def"
  Context &Ctx;

  Type(llvm::Type *LLVMTy, Context &Ctx) : LLVMTy(LLVMTy), Ctx(Ctx) {}
  friend class Context; // For constructor and ~Type().
  ~Type() = default;

public:
  /// Print the current type.
  /// Omit the type details if \p NoDetails == true.
  /// E.g., let %st = type { i32, i16 }
  /// When \p NoDetails is true, we only print %st.
  /// Put differently, \p NoDetails prints the type as if
  /// inlined with the operands when printing an instruction.
  void print(raw_ostream &OS, bool IsForDebug = false,
             bool NoDetails = false) const {
    LLVMTy->print(OS, IsForDebug, NoDetails);
  }

  Context &getContext() const { return Ctx; }

  /// Return true if this is 'void'.
  bool isVoidTy() const { return LLVMTy->isVoidTy(); }

  /// Return true if this is 'half', a 16-bit IEEE fp type.
  bool isHalfTy() const { return LLVMTy->isHalfTy(); }

  /// Return true if this is 'bfloat', a 16-bit bfloat type.
  bool isBFloatTy() const { return LLVMTy->isBFloatTy(); }

  /// Return true if this is a 16-bit float type.
  bool is16bitFPTy() const { return LLVMTy->is16bitFPTy(); }

  /// Return true if this is 'float', a 32-bit IEEE fp type.
  bool isFloatTy() const { return LLVMTy->isFloatTy(); }

  /// Return true if this is 'double', a 64-bit IEEE fp type.
  bool isDoubleTy() const { return LLVMTy->isDoubleTy(); }

  /// Return true if this is x86 long double.
  bool isX86_FP80Ty() const { return LLVMTy->isX86_FP80Ty(); }

  /// Return true if this is 'fp128'.
  bool isFP128Ty() const { return LLVMTy->isFP128Ty(); }

  /// Return true if this is powerpc long double.
  bool isPPC_FP128Ty() const { return LLVMTy->isPPC_FP128Ty(); }

  /// Return true if this is a well-behaved IEEE-like type, which has a IEEE
  /// compatible layout as defined by APFloat::isIEEE(), and does not have
  /// non-IEEE values, such as x86_fp80's unnormal values.
  bool isIEEELikeFPTy() const { return LLVMTy->isIEEELikeFPTy(); }

  /// Return true if this is one of the floating-point types
  bool isFloatingPointTy() const { return LLVMTy->isFloatingPointTy(); }

  /// Returns true if this is a floating-point type that is an unevaluated sum
  /// of multiple floating-point units.
  /// An example of such a type is ppc_fp128, also known as double-double, which
  /// consists of two IEEE 754 doubles.
  bool isMultiUnitFPType() const { return LLVMTy->isMultiUnitFPType(); }

  const fltSemantics &getFltSemantics() const {
    return LLVMTy->getFltSemantics();
  }

  /// Return true if this is X86 AMX.
  bool isX86_AMXTy() const { return LLVMTy->isX86_AMXTy(); }

  /// Return true if this is a target extension type.
  bool isTargetExtTy() const { return LLVMTy->isTargetExtTy(); }

  /// Return true if this is a target extension type with a scalable layout.
  bool isScalableTargetExtTy() const { return LLVMTy->isScalableTargetExtTy(); }

  /// Return true if this is a type whose size is a known multiple of vscale.
  bool isScalableTy() const { return LLVMTy->isScalableTy(); }

  /// Return true if this is a FP type or a vector of FP.
  bool isFPOrFPVectorTy() const { return LLVMTy->isFPOrFPVectorTy(); }

  /// Return true if this is 'label'.
  bool isLabelTy() const { return LLVMTy->isLabelTy(); }

  /// Return true if this is 'metadata'.
  bool isMetadataTy() const { return LLVMTy->isMetadataTy(); }

  /// Return true if this is 'token'.
  bool isTokenTy() const { return LLVMTy->isTokenTy(); }

  /// True if this is an instance of IntegerType.
  bool isIntegerTy() const { return LLVMTy->isIntegerTy(); }

  /// Return true if this is an IntegerType of the given width.
  bool isIntegerTy(unsigned Bitwidth) const {
    return LLVMTy->isIntegerTy(Bitwidth);
  }

  /// Return true if this is an integer type or a vector of integer types.
  bool isIntOrIntVectorTy() const { return LLVMTy->isIntOrIntVectorTy(); }

  /// Return true if this is an integer type or a vector of integer types of
  /// the given width.
  bool isIntOrIntVectorTy(unsigned BitWidth) const {
    return LLVMTy->isIntOrIntVectorTy(BitWidth);
  }

  /// Return true if this is an integer type or a pointer type.
  bool isIntOrPtrTy() const { return LLVMTy->isIntOrPtrTy(); }

  /// True if this is an instance of FunctionType.
  bool isFunctionTy() const { return LLVMTy->isFunctionTy(); }

  /// True if this is an instance of StructType.
  bool isStructTy() const { return LLVMTy->isStructTy(); }

  /// True if this is an instance of ArrayType.
  bool isArrayTy() const { return LLVMTy->isArrayTy(); }

  /// True if this is an instance of PointerType.
  bool isPointerTy() const { return LLVMTy->isPointerTy(); }

  /// Return true if this is a pointer type or a vector of pointer types.
  bool isPtrOrPtrVectorTy() const { return LLVMTy->isPtrOrPtrVectorTy(); }

  /// True if this is an instance of VectorType.
  inline bool isVectorTy() const { return LLVMTy->isVectorTy(); }

  /// Return true if this type could be converted with a lossless BitCast to
  /// type 'Ty'. For example, i8* to i32*. BitCasts are valid for types of the
  /// same size only where no re-interpretation of the bits is done.
  /// Determine if this type could be losslessly bitcast to Ty
  bool canLosslesslyBitCastTo(Type *Ty) const {
    return LLVMTy->canLosslesslyBitCastTo(Ty->LLVMTy);
  }

  /// Return true if this type is empty, that is, it has no elements or all of
  /// its elements are empty.
  bool isEmptyTy() const { return LLVMTy->isEmptyTy(); }

  /// Return true if the type is "first class", meaning it is a valid type for a
  /// Value.
  bool isFirstClassType() const { return LLVMTy->isFirstClassType(); }

  /// Return true if the type is a valid type for a register in codegen. This
  /// includes all first-class types except struct and array types.
  bool isSingleValueType() const { return LLVMTy->isSingleValueType(); }

  /// Return true if the type is an aggregate type. This means it is valid as
  /// the first operand of an insertvalue or extractvalue instruction. This
  /// includes struct and array types, but does not include vector types.
  bool isAggregateType() const { return LLVMTy->isAggregateType(); }

  /// Return true if it makes sense to take the size of this type. To get the
  /// actual size for a particular target, it is reasonable to use the
  /// DataLayout subsystem to do this.
  bool isSized(SmallPtrSetImpl<Type *> *Visited = nullptr) const {
    SmallPtrSet<llvm::Type *, 8> LLVMVisited;
    LLVMVisited.reserve(Visited->size());
    for (Type *Ty : *Visited)
      LLVMVisited.insert(Ty->LLVMTy);
    return LLVMTy->isSized(&LLVMVisited);
  }

  /// Return the basic size of this type if it is a primitive type. These are
  /// fixed by LLVM and are not target-dependent.
  /// This will return zero if the type does not have a size or is not a
  /// primitive type.
  ///
  /// If this is a scalable vector type, the scalable property will be set and
  /// the runtime size will be a positive integer multiple of the base size.
  ///
  /// Note that this may not reflect the size of memory allocated for an
  /// instance of the type or the number of bytes that are written when an
  /// instance of the type is stored to memory. The DataLayout class provides
  /// additional query functions to provide this information.
  ///
  TypeSize getPrimitiveSizeInBits() const {
    return LLVMTy->getPrimitiveSizeInBits();
  }

  /// If this is a vector type, return the getPrimitiveSizeInBits value for the
  /// element type. Otherwise return the getPrimitiveSizeInBits value for this
  /// type.
  unsigned getScalarSizeInBits() const { return LLVMTy->getScalarSizeInBits(); }

  /// Return the width of the mantissa of this type. This is only valid on
  /// floating-point types. If the FP type does not have a stable mantissa (e.g.
  /// ppc long double), this method returns -1.
  int getFPMantissaWidth() const { return LLVMTy->getFPMantissaWidth(); }

  /// Return whether the type is IEEE compatible, as defined by the eponymous
  /// method in APFloat.
  bool isIEEE() const { return LLVMTy->isIEEE(); }

  /// If this is a vector type, return the element type, otherwise return
  /// 'this'.
  Type *getScalarType() const;

  // TODO: ADD MISSING

  static Type *getInt64Ty(Context &Ctx);
  static Type *getInt32Ty(Context &Ctx);
  static Type *getInt16Ty(Context &Ctx);
  static Type *getInt8Ty(Context &Ctx);
  static Type *getInt1Ty(Context &Ctx);
  static Type *getDoubleTy(Context &Ctx);
  static Type *getFloatTy(Context &Ctx);
  // TODO: missing get*

  /// Get the address space of this pointer or pointer vector type.
  inline unsigned getPointerAddressSpace() const {
    return LLVMTy->getPointerAddressSpace();
  }

#ifndef NDEBUG
  void dumpOS(raw_ostream &OS);
  LLVM_DUMP_METHOD void dump();
#endif // NDEBUG
};

class PointerType : public Type {
public:
  // TODO: add missing functions

  static PointerType *get(Context &Ctx, unsigned AddressSpace);

  static bool classof(const Type *From) {
    return isa<llvm::PointerType>(From->LLVMTy);
  }
};

class ArrayType : public Type {
public:
  static ArrayType *get(Type *ElementType, uint64_t NumElements);
  // TODO: add missing functions
  static bool classof(const Type *From) {
    return isa<llvm::ArrayType>(From->LLVMTy);
  }
};

class StructType : public Type {
public:
  /// This static method is the primary way to create a literal StructType.
  static StructType *get(Context &Ctx, ArrayRef<Type *> Elements,
                         bool IsPacked = false);

  bool isPacked() const { return cast<llvm::StructType>(LLVMTy)->isPacked(); }

  // TODO: add missing functions
  static bool classof(const Type *From) {
    return isa<llvm::StructType>(From->LLVMTy);
  }
};

class VectorType : public Type {
public:
  static VectorType *get(Type *ElementType, ElementCount EC);
  static VectorType *get(Type *ElementType, unsigned NumElements,
                         bool Scalable) {
    return VectorType::get(ElementType,
                           ElementCount::get(NumElements, Scalable));
  }
  Type *getElementType() const;

  static VectorType *get(Type *ElementType, const VectorType *Other) {
    return VectorType::get(ElementType, Other->getElementCount());
  }

  inline ElementCount getElementCount() const {
    return cast<llvm::VectorType>(LLVMTy)->getElementCount();
  }
  static VectorType *getInteger(VectorType *VTy);
  static VectorType *getExtendedElementVectorType(VectorType *VTy);
  static VectorType *getTruncatedElementVectorType(VectorType *VTy);
  static VectorType *getSubdividedVectorType(VectorType *VTy, int NumSubdivs);
  static VectorType *getHalfElementsVectorType(VectorType *VTy);
  static VectorType *getDoubleElementsVectorType(VectorType *VTy);
  static bool isValidElementType(Type *ElemTy);

  static bool classof(const Type *From) {
    return isa<llvm::VectorType>(From->LLVMTy);
  }
};

class FixedVectorType : public VectorType {
public:
  static FixedVectorType *get(Type *ElementType, unsigned NumElts);

  static FixedVectorType *get(Type *ElementType, const FixedVectorType *FVTy) {
    return get(ElementType, FVTy->getNumElements());
  }

  static FixedVectorType *getInteger(FixedVectorType *VTy) {
    return cast<FixedVectorType>(VectorType::getInteger(VTy));
  }

  static FixedVectorType *getExtendedElementVectorType(FixedVectorType *VTy) {
    return cast<FixedVectorType>(VectorType::getExtendedElementVectorType(VTy));
  }

  static FixedVectorType *getTruncatedElementVectorType(FixedVectorType *VTy) {
    return cast<FixedVectorType>(
        VectorType::getTruncatedElementVectorType(VTy));
  }

  static FixedVectorType *getSubdividedVectorType(FixedVectorType *VTy,
                                                  int NumSubdivs) {
    return cast<FixedVectorType>(
        VectorType::getSubdividedVectorType(VTy, NumSubdivs));
  }

  static FixedVectorType *getHalfElementsVectorType(FixedVectorType *VTy) {
    return cast<FixedVectorType>(VectorType::getHalfElementsVectorType(VTy));
  }

  static FixedVectorType *getDoubleElementsVectorType(FixedVectorType *VTy) {
    return cast<FixedVectorType>(VectorType::getDoubleElementsVectorType(VTy));
  }

  static bool classof(const Type *T) {
    return isa<llvm::FixedVectorType>(T->LLVMTy);
  }

  unsigned getNumElements() const {
    return cast<llvm::FixedVectorType>(LLVMTy)->getNumElements();
  }
};

class ScalableVectorType : public VectorType {
public:
  static ScalableVectorType *get(Type *ElementType, unsigned MinNumElts);

  static ScalableVectorType *get(Type *ElementType,
                                 const ScalableVectorType *SVTy) {
    return get(ElementType, SVTy->getMinNumElements());
  }

  static ScalableVectorType *getInteger(ScalableVectorType *VTy) {
    return cast<ScalableVectorType>(VectorType::getInteger(VTy));
  }

  static ScalableVectorType *
  getExtendedElementVectorType(ScalableVectorType *VTy) {
    return cast<ScalableVectorType>(
        VectorType::getExtendedElementVectorType(VTy));
  }

  static ScalableVectorType *
  getTruncatedElementVectorType(ScalableVectorType *VTy) {
    return cast<ScalableVectorType>(
        VectorType::getTruncatedElementVectorType(VTy));
  }

  static ScalableVectorType *getSubdividedVectorType(ScalableVectorType *VTy,
                                                     int NumSubdivs) {
    return cast<ScalableVectorType>(
        VectorType::getSubdividedVectorType(VTy, NumSubdivs));
  }

  static ScalableVectorType *
  getHalfElementsVectorType(ScalableVectorType *VTy) {
    return cast<ScalableVectorType>(VectorType::getHalfElementsVectorType(VTy));
  }

  static ScalableVectorType *
  getDoubleElementsVectorType(ScalableVectorType *VTy) {
    return cast<ScalableVectorType>(
        VectorType::getDoubleElementsVectorType(VTy));
  }

  unsigned getMinNumElements() const {
    return cast<llvm::ScalableVectorType>(LLVMTy)->getMinNumElements();
  }

  static bool classof(const Type *T) {
    return isa<llvm::ScalableVectorType>(T->LLVMTy);
  }
};

class FunctionType : public Type {
public:
  // TODO: add missing functions
  static bool classof(const Type *From) {
    return isa<llvm::FunctionType>(From->LLVMTy);
  }
};

/// Class to represent integer types. Note that this class is also used to
/// represent the built-in integer types: Int1Ty, Int8Ty, Int16Ty, Int32Ty and
/// Int64Ty.
/// Integer representation type
class IntegerType : public Type {
public:
  static IntegerType *get(Context &C, unsigned NumBits);
  // TODO: add missing functions
  static bool classof(const Type *From) {
    return isa<llvm::IntegerType>(From->LLVMTy);
  }
  operator llvm::IntegerType &() const {
    return *cast<llvm::IntegerType>(LLVMTy);
  }
};

} // namespace llvm::sandboxir

#endif // LLVM_SANDBOXIR_TYPE_H
