/*========================== begin_copyright_notice ============================

Copyright (C) 2018-2022 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#ifndef IGCLLVM_IR_CONSTANTFOLDER_H
#define IGCLLVM_IR_CONSTANTFOLDER_H

#include "llvm/Config/llvm-config.h"
#include "llvm/IR/ConstantFolder.h"
#include <llvm/Support/Casting.h>

namespace IGCLLVM {
#if LLVM_VERSION_MAJOR <= 10
using ConstantFolderBase = llvm::ConstantFolder;
#else
// The main methods of the class now get proxied to an llvm::ConstantFolder
// instance so as to avoid letting `ConstantFolderBase` class become a
// pure-virtual class. Meanwhile, IGCConstantFolder itself is switched to
// inheriting from llvm::IRBuilderFolder to make it an acceptable template
// argument for the llvm::IRBuilder hierarchy.
// This was done because of a change in llvm::ConstantFolder class,
// which made it final, i.e. impossible to inherit from.
class ConstantFolderBase : public llvm::IRBuilderFolder {
private:
  llvm::ConstantFolder m_baseConstantFolder;

public:
  ConstantFolderBase() : m_baseConstantFolder(llvm::ConstantFolder()) {}

  /// -------------------------------------------------------------------
  /// This block defines virtual methods that are present in all versions
  /// of the base llvm::IRBuilderFolder class prior to LLVM 14, and
  /// wrapper methods that are needed for build compability accross all
  /// versions prior to LLVM 15.
  /// -------------------------------------------------------------------

#if LLVM_VERSION_MAJOR < 14
  inline llvm::Constant *CreateAdd(llvm::Constant *LHS, llvm::Constant *RHS, bool HasNUW = false,
                                   bool HasNSW = false) const override {
    return m_baseConstantFolder.CreateAdd(LHS, RHS, HasNUW, HasNSW);
  }

  inline llvm::Constant *CreateAnd(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateAnd(LHS, RHS);
  }

  inline llvm::Constant *CreateOr(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateOr(LHS, RHS);
  }

  inline llvm::Constant *CreateICmp(llvm::CmpInst::Predicate P, llvm::Constant *LHS,
                                    llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateICmp(P, LHS, RHS);
  }

  inline llvm::Constant *CreateSelect(llvm::Constant *C, llvm::Constant *True, llvm::Constant *False) const override {
    return m_baseConstantFolder.CreateSelect(C, True, False);
  }

  inline llvm::Constant *CreateGetElementPtr(llvm::Type *Ty, llvm::Constant *C,
                                             llvm::ArrayRef<llvm::Constant *> IdxList) const override {
    return m_baseConstantFolder.CreateGetElementPtr(Ty, C, IdxList);
  }

  inline llvm::Constant *CreateGetElementPtr(llvm::Type *Ty, llvm::Constant *C, llvm::Constant *Idx) const override {
    return m_baseConstantFolder.CreateGetElementPtr(Ty, C, Idx);
  }

  inline llvm::Constant *CreateGetElementPtr(llvm::Type *Ty, llvm::Constant *C,
                                             llvm::ArrayRef<llvm::Value *> IdxList) const override {
    return m_baseConstantFolder.CreateGetElementPtr(Ty, C, IdxList);
  }

  inline llvm::Constant *CreateInBoundsGetElementPtr(llvm::Type *Ty, llvm::Constant *C,
                                                     llvm::ArrayRef<llvm::Constant *> IdxList) const override {
    return m_baseConstantFolder.CreateInBoundsGetElementPtr(Ty, C, IdxList);
  }

  inline llvm::Constant *CreateInBoundsGetElementPtr(llvm::Type *Ty, llvm::Constant *C,
                                                     llvm::Constant *Idx) const override {
    return m_baseConstantFolder.CreateInBoundsGetElementPtr(Ty, C, Idx);
  }

  inline llvm::Constant *CreateInBoundsGetElementPtr(llvm::Type *Ty, llvm::Constant *C,
                                                     llvm::ArrayRef<llvm::Value *> IdxList) const override {
    return m_baseConstantFolder.CreateInBoundsGetElementPtr(Ty, C, IdxList);
  }
#endif // LLVM_VERSION_MAJOR < 14

  /// -------------------------------------------------------------------
  /// This block defines virtual methods that are present in all versions
  /// of the base llvm::IRBuilderFolder class up to LLVM 14, and wrapper
  /// methods that are needed for build compability accross all versions
  /// up to LLVM 14.
  /// -------------------------------------------------------------------

#if (LLVM_VERSION_MAJOR < 15) || defined(IGC_LLVM_TRUNK_REVISION)

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Value *FoldAdd(llvm::Value *LHS, llvm::Value *RHS, bool HasNUW = false, bool HasNSW = false) const
#if LLVM_VERSION_MAJOR < 14
  {
    return CreateAdd(llvm::dyn_cast<llvm::Constant>(LHS), llvm::dyn_cast<llvm::Constant>(RHS), HasNUW, HasNSW);
  }
#else
      override {
    return m_baseConstantFolder.FoldAdd(LHS, RHS, HasNUW, HasNSW);
  }
#endif

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Value *FoldAnd(llvm::Value *LHS, llvm::Value *RHS) const
#if LLVM_VERSION_MAJOR < 14
  {
    return CreateAnd(llvm::dyn_cast<llvm::Constant>(LHS), llvm::dyn_cast<llvm::Constant>(RHS));
  }
#else
      override {
    return m_baseConstantFolder.FoldAnd(LHS, RHS);
  }
#endif

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Value *FoldOr(llvm::Value *LHS, llvm::Value *RHS) const
#if LLVM_VERSION_MAJOR < 14
  {
    return CreateOr(llvm::dyn_cast<llvm::Constant>(LHS), llvm::dyn_cast<llvm::Constant>(RHS));
  }
#else
      override {
    return m_baseConstantFolder.FoldOr(LHS, RHS);
  }
#endif

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateFAdd(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateFAdd(LHS, RHS);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateSub(llvm::Constant *LHS, llvm::Constant *RHS, bool HasNUW = false,
                                   bool HasNSW = false) const override {
    return m_baseConstantFolder.CreateSub(LHS, RHS, HasNUW, HasNSW);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateFSub(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateFSub(LHS, RHS);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateMul(llvm::Constant *LHS, llvm::Constant *RHS, bool HasNUW = false,
                                   bool HasNSW = false) const override {
    return m_baseConstantFolder.CreateMul(LHS, RHS, HasNUW, HasNSW);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateFMul(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateFMul(LHS, RHS);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateUDiv(llvm::Constant *LHS, llvm::Constant *RHS, bool isExact = false) const override {
    return m_baseConstantFolder.CreateUDiv(LHS, RHS, isExact);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateSDiv(llvm::Constant *LHS, llvm::Constant *RHS, bool isExact = false) const override {
    return m_baseConstantFolder.CreateSDiv(LHS, RHS, isExact);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateFDiv(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateFDiv(LHS, RHS);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateURem(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateURem(LHS, RHS);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateSRem(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateSRem(LHS, RHS);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateFRem(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateFRem(LHS, RHS);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateShl(llvm::Constant *LHS, llvm::Constant *RHS, bool HasNUW = false,
                                   bool HasNSW = false) const override {
    return m_baseConstantFolder.CreateShl(LHS, RHS, HasNUW, HasNSW);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateLShr(llvm::Constant *LHS, llvm::Constant *RHS, bool isExact = false) const override {
    return m_baseConstantFolder.CreateLShr(LHS, RHS, isExact);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateAShr(llvm::Constant *LHS, llvm::Constant *RHS, bool isExact = false) const override {
    return m_baseConstantFolder.CreateAShr(LHS, RHS, isExact);
  }

  // Note: for direct usage in code, prefer `CreateBinOp` wrapper for all
  // LLVM versions up to 15.
  inline llvm::Constant *CreateXor(llvm::Constant *LHS, llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateXor(LHS, RHS);
  }

  inline llvm::Constant *CreateNeg(llvm::Constant *C, bool HasNUW = false, bool HasNSW = false) const override {
    return m_baseConstantFolder.CreateNeg(C, HasNUW, HasNSW);
  }

  inline llvm::Constant *CreateFNeg(llvm::Constant *C) const override { return m_baseConstantFolder.CreateFNeg(C); }

  inline llvm::Constant *CreateNot(llvm::Constant *C) const override { return m_baseConstantFolder.CreateNot(C); }

  inline llvm::Constant *CreateUnOp(llvm::Instruction::UnaryOps Opc, llvm::Constant *C) const override {
    return m_baseConstantFolder.CreateUnOp(Opc, C);
  }

  inline llvm::Constant *CreateExtractElement(llvm::Constant *Vec, llvm::Constant *Idx) const override {
    return m_baseConstantFolder.CreateExtractElement(Vec, Idx);
  }

  inline llvm::Constant *CreateInsertElement(llvm::Constant *Vec, llvm::Constant *NewElt,
                                             llvm::Constant *Idx) const override {
    return m_baseConstantFolder.CreateInsertElement(Vec, NewElt, Idx);
  }

  inline llvm::Constant *CreateShuffleVector(llvm::Constant *V1, llvm::Constant *V2,
                                             llvm::ArrayRef<int> Mask) const override {
    return m_baseConstantFolder.CreateShuffleVector(V1, V2, Mask);
  }

  inline llvm::Constant *CreateExtractValue(llvm::Constant *Agg, llvm::ArrayRef<unsigned> IdxList) const override {
    return m_baseConstantFolder.CreateExtractValue(Agg, IdxList);
  }

  inline llvm::Constant *CreateInsertValue(llvm::Constant *Agg, llvm::Constant *Val,
                                           llvm::ArrayRef<unsigned> IdxList) const override {
    return m_baseConstantFolder.CreateInsertValue(Agg, Val, IdxList);
  }
#endif // (LLVM_VERSION_MAJOR < 15) || defined(IGC_LLVM_TRUNK_REVISION)

  /// -------------------------------------------------------------------
  /// This block defines virtual methods that are present in the base
  /// llvm::IRBuilderFolder class starting from LLVM 15.
  /// -------------------------------------------------------------------

#if (LLVM_VERSION_MAJOR >= 15) && !defined(IGC_LLVM_TRUNK_REVISION)
  inline llvm::Value *FoldBinOp(llvm::Instruction::BinaryOps Opc, llvm::Value *LHS, llvm::Value *RHS) const override {
    return m_baseConstantFolder.FoldBinOp(Opc, LHS, RHS);
  }

  inline llvm::Value *FoldExactBinOp(llvm::Instruction::BinaryOps Opc, llvm::Value *LHS, llvm::Value *RHS,
                                     bool IsExact) const override {
    return m_baseConstantFolder.FoldExactBinOp(Opc, LHS, RHS, IsExact);
  }

  inline llvm::Value *FoldNoWrapBinOp(llvm::Instruction::BinaryOps Opc, llvm::Value *LHS, llvm::Value *RHS, bool HasNUW,
                                      bool HasNSW) const override {
    return m_baseConstantFolder.FoldNoWrapBinOp(Opc, LHS, RHS, HasNUW, HasNSW);
  }

  inline llvm::Value *FoldBinOpFMF(llvm::Instruction::BinaryOps Opc, llvm::Value *LHS, llvm::Value *RHS,
                                   llvm::FastMathFlags FMF) const override {
    return m_baseConstantFolder.FoldBinOpFMF(Opc, LHS, RHS, FMF);
  }

  inline llvm::Value *FoldUnOpFMF(llvm::Instruction::UnaryOps Opc, llvm::Value *V,
                                  llvm::FastMathFlags FMF) const override {
    return m_baseConstantFolder.FoldUnOpFMF(Opc, V, FMF);
  }

  inline llvm::Value *FoldExtractValue(llvm::Value *Agg, llvm::ArrayRef<unsigned> IdxList) const override {
    return m_baseConstantFolder.FoldExtractValue(Agg, IdxList);
  }

  inline llvm::Value *FoldInsertValue(llvm::Value *Agg, llvm::Value *Val,
                                      llvm::ArrayRef<unsigned> IdxList) const override {
    return m_baseConstantFolder.FoldInsertValue(Agg, Val, IdxList);
  }

  inline llvm::Value *FoldExtractElement(llvm::Value *Vec, llvm::Value *Idx) const override {
    return m_baseConstantFolder.FoldExtractElement(Vec, Idx);
  }

  inline llvm::Value *FoldInsertElement(llvm::Value *Vec, llvm::Value *NewElt, llvm::Value *Idx) const override {
    return m_baseConstantFolder.FoldInsertElement(Vec, NewElt, Idx);
  }

  inline llvm::Value *FoldShuffleVector(llvm::Value *V1, llvm::Value *V2, llvm::ArrayRef<int> Mask) const override {
    return m_baseConstantFolder.FoldShuffleVector(V1, V2, Mask);
  }
#endif // LLVM_VERSION_MAJOR >= 15

  /// -------------------------------------------------------------------
  /// This block defines virtual methods that are present in all versions
  /// of the base llvm::IRBuilderFolder class up to LLVM 15, and wrapper
  /// methods that are needed for build compability accross all versions
  /// up to LLVM 15.
  /// -------------------------------------------------------------------

  inline llvm::Constant *CreateBinOp(llvm::Instruction::BinaryOps Opc, llvm::Constant *LHS, llvm::Constant *RHS) const
#if (LLVM_VERSION_MAJOR < 15) || defined(IGC_LLVM_TRUNK_REVISION)
      override {
    return m_baseConstantFolder.CreateBinOp(Opc, LHS, RHS);
  }
#else
  {
    return llvm::cast_or_null<llvm::Constant>(m_baseConstantFolder.FoldBinOp(Opc, llvm::cast_or_null<llvm::Value>(LHS),
                                                                             llvm::cast_or_null<llvm::Value>(RHS)));
  }
#endif

  inline llvm::Value *FoldICmp(llvm::CmpInst::Predicate P, llvm::Value *LHS, llvm::Value *RHS) const
#if LLVM_VERSION_MAJOR < 14
  {
    return CreateICmp(P, llvm::dyn_cast<llvm::Constant>(LHS), llvm::dyn_cast<llvm::Constant>(RHS));
  }
#else
      override {
    return m_baseConstantFolder.FoldICmp(P, LHS, RHS);
  }
#endif

  inline llvm::Value *FoldSelect(llvm::Value *C, llvm::Value *True, llvm::Value *False) const
#if LLVM_VERSION_MAJOR < 14
  {
    return CreateSelect(llvm::dyn_cast<llvm::Constant>(C), llvm::dyn_cast<llvm::Constant>(True),
                        llvm::dyn_cast<llvm::Constant>(False));
  }
#else
      override {
    return m_baseConstantFolder.FoldSelect(C, True, False);
  }
#endif

  inline llvm::Value *FoldGEP(llvm::Type *Ty, llvm::Value *Ptr, llvm::ArrayRef<llvm::Value *> IdxList,
                              bool IsInBounds = false) const
#if LLVM_VERSION_MAJOR < 14
  {
    if (IsInBounds) {
      return CreateInBoundsGetElementPtr(Ty, llvm::dyn_cast<llvm::Constant>(Ptr), IdxList);
    } else {
      return CreateGetElementPtr(Ty, llvm::dyn_cast<llvm::Constant>(Ptr), IdxList);
    }
  }
#else
      override {
    return m_baseConstantFolder.FoldGEP(Ty, Ptr, IdxList, IsInBounds);
  }
#endif

  inline llvm::Constant *CreateCast(llvm::Instruction::CastOps Op, llvm::Constant *C,
                                    llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreateCast(Op, C, DestTy);
  }

  inline llvm::Constant *CreatePointerCast(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreatePointerCast(C, DestTy);
  }

  inline llvm::Constant *CreatePointerBitCastOrAddrSpaceCast(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreatePointerBitCastOrAddrSpaceCast(C, DestTy);
  }

  inline llvm::Constant *CreateIntCast(llvm::Constant *C, llvm::Type *DestTy, bool isSigned) const override {
    return m_baseConstantFolder.CreateIntCast(C, DestTy, isSigned);
  }

  inline llvm::Constant *CreateFPCast(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreateFPCast(C, DestTy);
  }

  inline llvm::Constant *CreateBitCast(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreateCast(llvm::Instruction::BitCast, C, DestTy);
  }

  inline llvm::Constant *CreateIntToPtr(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreateCast(llvm::Instruction::IntToPtr, C, DestTy);
  }

  inline llvm::Constant *CreatePtrToInt(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreateCast(llvm::Instruction::PtrToInt, C, DestTy);
  }

  inline llvm::Constant *CreateZExtOrBitCast(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreateZExtOrBitCast(C, DestTy);
  }

  inline llvm::Constant *CreateSExtOrBitCast(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreateSExtOrBitCast(C, DestTy);
  }

  inline llvm::Constant *CreateTruncOrBitCast(llvm::Constant *C, llvm::Type *DestTy) const override {
    return m_baseConstantFolder.CreateTruncOrBitCast(C, DestTy);
  }

  inline llvm::Constant *CreateFCmp(llvm::CmpInst::Predicate P, llvm::Constant *LHS,
                                    llvm::Constant *RHS) const override {
    return m_baseConstantFolder.CreateFCmp(P, LHS, RHS);
  }
};
#endif
} // namespace IGCLLVM

#endif // IGCLLVM_IR_CONSTANTFOLDER_H
