//===- MipsRegisterBankInfo.h -----------------------------------*- 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
//
//===----------------------------------------------------------------------===//
/// \file
/// This file declares the targeting of the RegisterBankInfo class for Mips.
/// \todo This should be generated by TableGen.
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIB_TARGET_MIPS_MIPSREGISTERBANKINFO_H
#define LLVM_LIB_TARGET_MIPS_MIPSREGISTERBANKINFO_H

#include "llvm/CodeGen/RegisterBankInfo.h"

#define GET_REGBANK_DECLARATIONS
#include "MipsGenRegisterBank.inc"

namespace llvm {

class TargetRegisterInfo;

class MipsGenRegisterBankInfo : public RegisterBankInfo {
#define GET_TARGET_REGBANK_CLASS
#include "MipsGenRegisterBank.inc"
};

/// This class provides the information for the target register banks.
class MipsRegisterBankInfo final : public MipsGenRegisterBankInfo {
public:
  MipsRegisterBankInfo(const TargetRegisterInfo &TRI);

  const RegisterBank &getRegBankFromRegClass(const TargetRegisterClass &RC,
                                             LLT) const override;

  const InstructionMapping &
  getInstrMapping(const MachineInstr &MI) const override;

  /// Here we have to narrowScalar s64 operands to s32, combine away G_MERGE or
  /// G_UNMERGE and erase instructions that became dead in the process. We
  /// manually assign bank to def operand of all new instructions that were
  /// created in the process since they will not end up in RegBankSelect loop.
  void applyMappingImpl(MachineIRBuilder &Builder,
                        const OperandsMapper &OpdMapper) const override;

  /// RegBankSelect determined that s64 operand is better to be split into two
  /// s32 operands in gprb. Here we manually set register banks of def operands
  /// of newly created instructions since they will not get regbankselected.
  void setRegBank(MachineInstr &MI, MachineRegisterInfo &MRI) const;

private:
  /// Some instructions are used with both floating point and integer operands.
  /// We assign InstType to such instructions as it helps us to avoid cross bank
  /// copies. InstType deppends on context.
  enum InstType {
    /// Temporary type, when visit(..., nullptr) finishes will convert to one of
    /// the remaining types: Integer, FloatingPoint or Ambiguous.
    NotDetermined,
    /// Connected with instruction that interprets 'bags of bits' as integers.
    /// Select gprb to avoid cross bank copies.
    Integer,
    /// Connected with instruction that interprets 'bags of bits' as floating
    /// point numbers. Select fprb to avoid cross bank copies.
    FloatingPoint,
    /// Represents moving 'bags of bits' around. Select same bank for entire
    /// chain to avoid cross bank copies. Currently we select fprb for s64 and
    /// gprb for s32 Ambiguous operands.
    Ambiguous,
    /// Only used for s64. Unlike Ambiguous s64, AmbiguousWithMergeOrUnmerge s64
    /// is mapped to gprb (legalized using narrow scalar to s32).
    AmbiguousWithMergeOrUnmerge
  };

  bool isAmbiguous_64(InstType InstTy, unsigned OpSize) const {
    if (InstTy == InstType::Ambiguous && OpSize == 64)
      return true;
    return false;
  }

  bool isAmbiguous_32(InstType InstTy, unsigned OpSize) const {
    if (InstTy == InstType::Ambiguous && OpSize == 32)
      return true;
    return false;
  }

  bool isAmbiguous_32or64(InstType InstTy, unsigned OpSize) const {
    if (InstTy == InstType::Ambiguous && (OpSize == 32 || OpSize == 64))
      return true;
    return false;
  }

  bool isAmbiguousWithMergeOrUnmerge_64(InstType InstTy,
                                        unsigned OpSize) const {
    if (InstTy == InstType::AmbiguousWithMergeOrUnmerge && OpSize == 64)
      return true;
    return false;
  }

  bool isFloatingPoint_32or64(InstType InstTy, unsigned OpSize) const {
    if (InstTy == InstType::FloatingPoint && (OpSize == 32 || OpSize == 64))
      return true;
    return false;
  }

  bool isFloatingPoint_64(InstType InstTy, unsigned OpSize) const {
    if (InstTy == InstType::FloatingPoint && OpSize == 64)
      return true;
    return false;
  }

  bool isInteger_32(InstType InstTy, unsigned OpSize) const {
    if (InstTy == InstType::Integer && OpSize == 32)
      return true;
    return false;
  }

  /// Some generic instructions have operands that can be mapped to either fprb
  /// or gprb e.g. for G_LOAD we consider only operand 0 as ambiguous, operand 1
  /// is always gprb since it is a pointer.
  /// This class provides containers for MI's ambiguous:
  /// DefUses : MachineInstrs that use one of MI's ambiguous def operands.
  /// UseDefs : MachineInstrs that define MI's ambiguous use operands.
  class AmbiguousRegDefUseContainer {
    SmallVector<MachineInstr *, 2> DefUses;
    SmallVector<MachineInstr *, 2> UseDefs;

    void addDefUses(Register Reg, const MachineRegisterInfo &MRI);
    void addUseDef(Register Reg, const MachineRegisterInfo &MRI);

    /// Skip copy instructions until we get to a non-copy instruction or to a
    /// copy with phys register as def. Used during search for DefUses.
    /// MI :  %5 = COPY %4
    ///       %6 = COPY %5
    ///       $v0 = COPY %6 <- we want this one.
    MachineInstr *skipCopiesOutgoing(MachineInstr *MI) const;

    /// Skip copy instructions until we get to a non-copy instruction or to a
    /// copy with phys register as use. Used during search for UseDefs.
    ///       %1 = COPY $a1 <- we want this one.
    ///       %2 = COPY %1
    /// MI =  %3 = COPY %2
    MachineInstr *skipCopiesIncoming(MachineInstr *MI) const;

  public:
    AmbiguousRegDefUseContainer(const MachineInstr *MI);
    SmallVectorImpl<MachineInstr *> &getDefUses() { return DefUses; }
    SmallVectorImpl<MachineInstr *> &getUseDefs() { return UseDefs; }
  };

  class TypeInfoForMF {
    /// MachineFunction name is used to recognise when MF changes.
    std::string MFName;
    /// <key, value> : value is vector of all MachineInstrs that are waiting for
    /// key to figure out type of some of its ambiguous operands.
    DenseMap<const MachineInstr *, SmallVector<const MachineInstr *, 2>>
        WaitingQueues;
    /// Recorded InstTypes for visited instructions.
    DenseMap<const MachineInstr *, InstType> Types;

    /// Recursively visit MI's adjacent instructions and find MI's InstType.
    bool visit(const MachineInstr *MI, const MachineInstr *WaitingForTypeOfMI,
               InstType &AmbiguousTy);

    /// Visit MI's adjacent UseDefs or DefUses.
    bool visitAdjacentInstrs(const MachineInstr *MI,
                             SmallVectorImpl<MachineInstr *> &AdjacentInstrs,
                             bool isDefUse, InstType &AmbiguousTy);

    /// Set type for MI, and recursively for all instructions that are
    /// waiting for MI's type.
    void setTypes(const MachineInstr *MI, InstType ITy);

    /// InstType for MI is determined, set it to InstType that corresponds to
    /// physical regisiter that is operand number Op in CopyInst.
    void setTypesAccordingToPhysicalRegister(const MachineInstr *MI,
                                             const MachineInstr *CopyInst,
                                             unsigned Op);

    /// Set default values for MI in order to start visit.
    void startVisit(const MachineInstr *MI) {
      Types.try_emplace(MI, InstType::NotDetermined);
      WaitingQueues.try_emplace(MI);
    }

    /// Returns true if instruction was already visited. Type might not be
    /// determined at this point but will be when visit(..., nullptr) finishes.
    bool wasVisited(const MachineInstr *MI) const { return Types.count(MI); };

    /// Returns recorded type for instruction.
    const InstType &getRecordedTypeForInstr(const MachineInstr *MI) const {
      assert(wasVisited(MI) && "Instruction was not visited!");
      return Types.find(MI)->getSecond();
    };

    /// Change recorded type for instruction.
    void changeRecordedTypeForInstr(const MachineInstr *MI, InstType InstTy) {
      assert(wasVisited(MI) && "Instruction was not visited!");
      Types.find(MI)->getSecond() = InstTy;
    };

    /// Returns WaitingQueue for instruction.
    const SmallVectorImpl<const MachineInstr *> &
    getWaitingQueueFor(const MachineInstr *MI) const {
      assert(WaitingQueues.count(MI) && "Instruction was not visited!");
      return WaitingQueues.find(MI)->getSecond();
    };

    /// Add WaitingForMI to MI's WaitingQueue.
    void addToWaitingQueue(const MachineInstr *MI,
                           const MachineInstr *WaitingForMI) {
      assert(WaitingQueues.count(MI) && "Instruction was not visited!");
      WaitingQueues.find(MI)->getSecond().push_back(WaitingForMI);
    };

  public:
    InstType determineInstType(const MachineInstr *MI);

    void cleanupIfNewFunction(llvm::StringRef FunctionName);

    /// MI is about to get destroyed (using narrow scalar). Internal data is
    /// saved based on MI's address, clear it since it is no longer valid.
    void clearTypeInfoData(const MachineInstr *MI) {
      Types.erase(MI);
      WaitingQueues.erase(MI);
    };
  };
};
} // end namespace llvm
#endif
