//===-- VERegisterInfo.cpp - VE Register Information ----------------------===//
//
// 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 file contains the VE implementation of the TargetRegisterInfo class.
//
//===----------------------------------------------------------------------===//

#include "VERegisterInfo.h"
#include "VE.h"
#include "VESubtarget.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/Debug.h"

using namespace llvm;

#define DEBUG_TYPE "ve-register-info"

#define GET_REGINFO_TARGET_DESC
#include "VEGenRegisterInfo.inc"

// VE uses %s10 == %lp to keep return address
VERegisterInfo::VERegisterInfo() : VEGenRegisterInfo(VE::SX10) {}

const MCPhysReg *
VERegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const {
  switch (MF->getFunction().getCallingConv()) {
  case CallingConv::Fast:
    // Being explicit (same as standard CC).
  default:
    return CSR_SaveList;
  case CallingConv::PreserveAll:
    return CSR_preserve_all_SaveList;
  }
}

const uint32_t *VERegisterInfo::getCallPreservedMask(const MachineFunction &MF,
                                                     CallingConv::ID CC) const {
  switch (CC) {
  case CallingConv::Fast:
    // Being explicit (same as standard CC).
  default:
    return CSR_RegMask;
  case CallingConv::PreserveAll:
    return CSR_preserve_all_RegMask;
  }
}

const uint32_t *VERegisterInfo::getNoPreservedMask() const {
  return CSR_NoRegs_RegMask;
}

BitVector VERegisterInfo::getReservedRegs(const MachineFunction &MF) const {
  BitVector Reserved(getNumRegs());

  const Register ReservedRegs[] = {
      VE::SX8,  // Stack limit
      VE::SX9,  // Frame pointer
      VE::SX10, // Link register (return address)
      VE::SX11, // Stack pointer

      // FIXME: maybe not need to be reserved
      VE::SX12, // Outer register
      VE::SX13, // Id register for dynamic linker

      VE::SX14, // Thread pointer
      VE::SX15, // Global offset table register
      VE::SX16, // Procedure linkage table register
      VE::SX17, // Linkage-area register
                // sx18-sx33 are callee-saved registers
                // sx34-sx63 are temporary registers
  };

  for (auto R : ReservedRegs)
    for (MCRegAliasIterator ItAlias(R, this, true); ItAlias.isValid();
         ++ItAlias)
      Reserved.set(*ItAlias);

  // Reserve constant registers.
  Reserved.set(VE::VM0);
  Reserved.set(VE::VMP0);

  return Reserved;
}

const TargetRegisterClass *
VERegisterInfo::getPointerRegClass(const MachineFunction &MF,
                                   unsigned Kind) const {
  return &VE::I64RegClass;
}

static unsigned offsetToDisp(MachineInstr &MI) {
  // Default offset in instruction's operands (reg+reg+imm).
  unsigned OffDisp = 2;

#define RRCAS_multi_cases(NAME) NAME##rir : case NAME##rii

  {
    using namespace llvm::VE;
    switch (MI.getOpcode()) {
    case INLINEASM:
    case RRCAS_multi_cases(TS1AML):
    case RRCAS_multi_cases(TS1AMW):
    case RRCAS_multi_cases(CASL):
    case RRCAS_multi_cases(CASW):
      // These instructions use AS format (reg+imm).
      OffDisp = 1;
      break;
    }
  }
#undef RRCAS_multi_cases

  return OffDisp;
}

namespace {
class EliminateFrameIndex {
  const TargetInstrInfo &TII;
  const TargetRegisterInfo &TRI;
  const DebugLoc &DL;
  MachineBasicBlock &MBB;
  MachineBasicBlock::iterator II;
  Register clobber;

  // Some helper functions for the ease of instruction building.
  MachineFunction &getFunc() const { return *MBB.getParent(); }
  inline MCRegister getSubReg(MCRegister Reg, unsigned Idx) const {
    return TRI.getSubReg(Reg, Idx);
  }
  inline const MCInstrDesc &get(unsigned Opcode) const {
    return TII.get(Opcode);
  }
  inline MachineInstrBuilder build(const MCInstrDesc &MCID, Register DestReg) {
    return BuildMI(MBB, II, DL, MCID, DestReg);
  }
  inline MachineInstrBuilder build(unsigned InstOpc, Register DestReg) {
    return build(get(InstOpc), DestReg);
  }
  inline MachineInstrBuilder build(const MCInstrDesc &MCID) {
    return BuildMI(MBB, II, DL, MCID);
  }
  inline MachineInstrBuilder build(unsigned InstOpc) {
    return build(get(InstOpc));
  }

  // Calculate an address of frame index from a frame register and a given
  // offset if the offset doesn't fit in the immediate field.  Use a clobber
  // register to hold calculated address.
  void prepareReplaceFI(MachineInstr &MI, Register &FrameReg, int64_t &Offset,
                        int64_t Bytes = 0);
  // Replace the frame index in \p MI with a frame register and a given offset
  // if it fits in the immediate field.  Otherwise, use pre-calculated address
  // in a clobber regsiter.
  void replaceFI(MachineInstr &MI, Register FrameReg, int64_t Offset,
                 int FIOperandNum);

  // Expand and eliminate Frame Index of pseudo STQrii and LDQrii.
  void processSTQ(MachineInstr &MI, Register FrameReg, int64_t Offset,
                  int FIOperandNum);
  void processLDQ(MachineInstr &MI, Register FrameReg, int64_t Offset,
                  int FIOperandNum);
  // Expand and eliminate Frame Index of pseudo STVMrii and LDVMrii.
  void processSTVM(MachineInstr &MI, Register FrameReg, int64_t Offset,
                   int FIOperandNum);
  void processLDVM(MachineInstr &MI, Register FrameReg, int64_t Offset,
                   int FIOperandNum);
  // Expand and eliminate Frame Index of pseudo STVM512rii and LDVM512rii.
  void processSTVM512(MachineInstr &MI, Register FrameReg, int64_t Offset,
                      int FIOperandNum);
  void processLDVM512(MachineInstr &MI, Register FrameReg, int64_t Offset,
                      int FIOperandNum);

public:
  EliminateFrameIndex(const TargetInstrInfo &TII, const TargetRegisterInfo &TRI,
                      const DebugLoc &DL, MachineBasicBlock &MBB,
                      MachineBasicBlock::iterator II)
      : TII(TII), TRI(TRI), DL(DL), MBB(MBB), II(II), clobber(VE::SX13) {}

  // Expand and eliminate Frame Index from MI
  void processMI(MachineInstr &MI, Register FrameReg, int64_t Offset,
                 int FIOperandNum);
};
} // namespace

// Prepare the frame index if it doesn't fit in the immediate field.  Use
// clobber register to hold calculated address.
void EliminateFrameIndex::prepareReplaceFI(MachineInstr &MI, Register &FrameReg,
                                           int64_t &Offset, int64_t Bytes) {
  if (isInt<32>(Offset) && isInt<32>(Offset + Bytes)) {
    // If the offset is small enough to fit in the immediate field, directly
    // encode it.  So, nothing to prepare here.
    return;
  }

  // If the offset doesn't fit, emit following codes.  This clobbers SX13
  // which we always know is available here.
  //   lea     %clobber, Offset@lo
  //   and     %clobber, %clobber, (32)0
  //   lea.sl  %clobber, Offset@hi(FrameReg, %clobber)
  build(VE::LEAzii, clobber).addImm(0).addImm(0).addImm(Lo_32(Offset));
  build(VE::ANDrm, clobber).addReg(clobber).addImm(M0(32));
  build(VE::LEASLrri, clobber)
      .addReg(clobber)
      .addReg(FrameReg)
      .addImm(Hi_32(Offset));

  // Use clobber register as a frame register and 0 offset
  FrameReg = clobber;
  Offset = 0;
}

// Replace the frame index in \p MI with a proper byte and framereg offset.
void EliminateFrameIndex::replaceFI(MachineInstr &MI, Register FrameReg,
                                    int64_t Offset, int FIOperandNum) {
  assert(isInt<32>(Offset));

  // The offset must be small enough to fit in the immediate field after
  // call of prepareReplaceFI.  Therefore, we directly encode it.
  MI.getOperand(FIOperandNum).ChangeToRegister(FrameReg, false);
  MI.getOperand(FIOperandNum + offsetToDisp(MI)).ChangeToImmediate(Offset);
}

void EliminateFrameIndex::processSTQ(MachineInstr &MI, Register FrameReg,
                                     int64_t Offset, int FIOperandNum) {
  assert(MI.getOpcode() == VE::STQrii);
  LLVM_DEBUG(dbgs() << "processSTQ: "; MI.dump());

  prepareReplaceFI(MI, FrameReg, Offset, 8);

  Register SrcReg = MI.getOperand(3).getReg();
  Register SrcHiReg = getSubReg(SrcReg, VE::sub_even);
  Register SrcLoReg = getSubReg(SrcReg, VE::sub_odd);
  // VE stores HiReg to 8(addr) and LoReg to 0(addr)
  MachineInstr *StMI =
      build(VE::STrii).addReg(FrameReg).addImm(0).addImm(0).addReg(SrcLoReg);
  replaceFI(*StMI, FrameReg, Offset, 0);
  // Mutate to 'hi' store.
  MI.setDesc(get(VE::STrii));
  MI.getOperand(3).setReg(SrcHiReg);
  Offset += 8;
  replaceFI(MI, FrameReg, Offset, FIOperandNum);
}

void EliminateFrameIndex::processLDQ(MachineInstr &MI, Register FrameReg,
                                     int64_t Offset, int FIOperandNum) {
  assert(MI.getOpcode() == VE::LDQrii);
  LLVM_DEBUG(dbgs() << "processLDQ: "; MI.dump());

  prepareReplaceFI(MI, FrameReg, Offset, 8);

  Register DestReg = MI.getOperand(0).getReg();
  Register DestHiReg = getSubReg(DestReg, VE::sub_even);
  Register DestLoReg = getSubReg(DestReg, VE::sub_odd);
  // VE loads HiReg from 8(addr) and LoReg from 0(addr)
  MachineInstr *StMI =
      build(VE::LDrii, DestLoReg).addReg(FrameReg).addImm(0).addImm(0);
  replaceFI(*StMI, FrameReg, Offset, 1);
  MI.setDesc(get(VE::LDrii));
  MI.getOperand(0).setReg(DestHiReg);
  Offset += 8;
  replaceFI(MI, FrameReg, Offset, FIOperandNum);
}

void EliminateFrameIndex::processSTVM(MachineInstr &MI, Register FrameReg,
                                      int64_t Offset, int FIOperandNum) {
  assert(MI.getOpcode() == VE::STVMrii);
  LLVM_DEBUG(dbgs() << "processSTVM: "; MI.dump());

  // Original MI is:
  //   STVMrii frame-index, 0, offset, reg (, memory operand)
  // Convert it to:
  //   SVMi   tmp-reg, reg, 0
  //   STrii  frame-reg, 0, offset, tmp-reg
  //   SVMi   tmp-reg, reg, 1
  //   STrii  frame-reg, 0, offset+8, tmp-reg
  //   SVMi   tmp-reg, reg, 2
  //   STrii  frame-reg, 0, offset+16, tmp-reg
  //   SVMi   tmp-reg, reg, 3
  //   STrii  frame-reg, 0, offset+24, tmp-reg

  prepareReplaceFI(MI, FrameReg, Offset, 24);

  Register SrcReg = MI.getOperand(3).getReg();
  bool isKill = MI.getOperand(3).isKill();
  // FIXME: it would be better to scavenge a register here instead of
  // reserving SX16 all of the time.
  Register TmpReg = VE::SX16;
  for (int i = 0; i < 3; ++i) {
    build(VE::SVMmr, TmpReg).addReg(SrcReg).addImm(i);
    MachineInstr *StMI =
        build(VE::STrii).addReg(FrameReg).addImm(0).addImm(0).addReg(
            TmpReg, getKillRegState(true));
    replaceFI(*StMI, FrameReg, Offset, 0);
    Offset += 8;
  }
  build(VE::SVMmr, TmpReg).addReg(SrcReg, getKillRegState(isKill)).addImm(3);
  MI.setDesc(get(VE::STrii));
  MI.getOperand(3).ChangeToRegister(TmpReg, false, false, true);
  replaceFI(MI, FrameReg, Offset, FIOperandNum);
}

void EliminateFrameIndex::processLDVM(MachineInstr &MI, Register FrameReg,
                                      int64_t Offset, int FIOperandNum) {
  assert(MI.getOpcode() == VE::LDVMrii);
  LLVM_DEBUG(dbgs() << "processLDVM: "; MI.dump());

  // Original MI is:
  //   LDVMri reg, frame-index, 0, offset (, memory operand)
  // Convert it to:
  //   LDrii  tmp-reg, frame-reg, 0, offset
  //   LVMir vm, 0, tmp-reg
  //   LDrii  tmp-reg, frame-reg, 0, offset+8
  //   LVMir_m vm, 1, tmp-reg, vm
  //   LDrii  tmp-reg, frame-reg, 0, offset+16
  //   LVMir_m vm, 2, tmp-reg, vm
  //   LDrii  tmp-reg, frame-reg, 0, offset+24
  //   LVMir_m vm, 3, tmp-reg, vm

  prepareReplaceFI(MI, FrameReg, Offset, 24);

  Register DestReg = MI.getOperand(0).getReg();
  // FIXME: it would be better to scavenge a register here instead of
  // reserving SX16 all of the time.
  unsigned TmpReg = VE::SX16;
  for (int i = 0; i < 4; ++i) {
    if (i != 3) {
      MachineInstr *StMI =
          build(VE::LDrii, TmpReg).addReg(FrameReg).addImm(0).addImm(0);
      replaceFI(*StMI, FrameReg, Offset, 1);
      Offset += 8;
    } else {
      // Last LDrii replace the target instruction.
      MI.setDesc(get(VE::LDrii));
      MI.getOperand(0).ChangeToRegister(TmpReg, true);
    }
    // First LVM is LVMir.  Others are LVMir_m.  Last LVM places at the
    // next of the target instruction.
    if (i == 0)
      build(VE::LVMir, DestReg).addImm(i).addReg(TmpReg, getKillRegState(true));
    else if (i != 3)
      build(VE::LVMir_m, DestReg)
          .addImm(i)
          .addReg(TmpReg, getKillRegState(true))
          .addReg(DestReg);
    else
      BuildMI(*MI.getParent(), std::next(II), DL, get(VE::LVMir_m), DestReg)
          .addImm(3)
          .addReg(TmpReg, getKillRegState(true))
          .addReg(DestReg);
  }
  replaceFI(MI, FrameReg, Offset, FIOperandNum);
}

void EliminateFrameIndex::processSTVM512(MachineInstr &MI, Register FrameReg,
                                         int64_t Offset, int FIOperandNum) {
  assert(MI.getOpcode() == VE::STVM512rii);
  LLVM_DEBUG(dbgs() << "processSTVM512: "; MI.dump());

  prepareReplaceFI(MI, FrameReg, Offset, 56);

  Register SrcReg = MI.getOperand(3).getReg();
  Register SrcLoReg = getSubReg(SrcReg, VE::sub_vm_odd);
  Register SrcHiReg = getSubReg(SrcReg, VE::sub_vm_even);
  bool isKill = MI.getOperand(3).isKill();
  // FIXME: it would be better to scavenge a register here instead of
  // reserving SX16 all of the time.
  Register TmpReg = VE::SX16;
  // store low part of VMP
  MachineInstr *LastMI = nullptr;
  for (int i = 0; i < 4; ++i) {
    LastMI = build(VE::SVMmr, TmpReg).addReg(SrcLoReg).addImm(i);
    MachineInstr *StMI =
        build(VE::STrii).addReg(FrameReg).addImm(0).addImm(0).addReg(
            TmpReg, getKillRegState(true));
    replaceFI(*StMI, FrameReg, Offset, 0);
    Offset += 8;
  }
  if (isKill)
    LastMI->addRegisterKilled(SrcLoReg, &TRI, true);
  // store high part of VMP
  for (int i = 0; i < 3; ++i) {
    build(VE::SVMmr, TmpReg).addReg(SrcHiReg).addImm(i);
    MachineInstr *StMI =
        build(VE::STrii).addReg(FrameReg).addImm(0).addImm(0).addReg(
            TmpReg, getKillRegState(true));
    replaceFI(*StMI, FrameReg, Offset, 0);
    Offset += 8;
  }
  LastMI = build(VE::SVMmr, TmpReg).addReg(SrcHiReg).addImm(3);
  if (isKill) {
    LastMI->addRegisterKilled(SrcHiReg, &TRI, true);
    // Add implicit super-register kills to the particular MI.
    LastMI->addRegisterKilled(SrcReg, &TRI, true);
  }
  MI.setDesc(get(VE::STrii));
  MI.getOperand(3).ChangeToRegister(TmpReg, false, false, true);
  replaceFI(MI, FrameReg, Offset, FIOperandNum);
}

void EliminateFrameIndex::processLDVM512(MachineInstr &MI, Register FrameReg,
                                         int64_t Offset, int FIOperandNum) {
  assert(MI.getOpcode() == VE::LDVM512rii);
  LLVM_DEBUG(dbgs() << "processLDVM512: "; MI.dump());

  prepareReplaceFI(MI, FrameReg, Offset, 56);

  Register DestReg = MI.getOperand(0).getReg();
  Register DestLoReg = getSubReg(DestReg, VE::sub_vm_odd);
  Register DestHiReg = getSubReg(DestReg, VE::sub_vm_even);
  // FIXME: it would be better to scavenge a register here instead of
  // reserving SX16 all of the time.
  Register TmpReg = VE::SX16;
  build(VE::IMPLICIT_DEF, DestReg);
  for (int i = 0; i < 4; ++i) {
    MachineInstr *LdMI =
        build(VE::LDrii, TmpReg).addReg(FrameReg).addImm(0).addImm(0);
    replaceFI(*LdMI, FrameReg, Offset, 1);
    build(VE::LVMir_m, DestLoReg)
        .addImm(i)
        .addReg(TmpReg, getKillRegState(true))
        .addReg(DestLoReg);
    Offset += 8;
  }
  for (int i = 0; i < 3; ++i) {
    MachineInstr *LdMI =
        build(VE::LDrii, TmpReg).addReg(FrameReg).addImm(0).addImm(0);
    replaceFI(*LdMI, FrameReg, Offset, 1);
    build(VE::LVMir_m, DestHiReg)
        .addImm(i)
        .addReg(TmpReg, getKillRegState(true))
        .addReg(DestHiReg);
    Offset += 8;
  }
  MI.setDesc(get(VE::LDrii));
  MI.getOperand(0).ChangeToRegister(TmpReg, true);
  BuildMI(*MI.getParent(), std::next(II), DL, get(VE::LVMir_m), DestHiReg)
      .addImm(3)
      .addReg(TmpReg, getKillRegState(true))
      .addReg(DestHiReg);
  replaceFI(MI, FrameReg, Offset, FIOperandNum);
}

void EliminateFrameIndex::processMI(MachineInstr &MI, Register FrameReg,
                                    int64_t Offset, int FIOperandNum) {
  switch (MI.getOpcode()) {
  case VE::STQrii:
    processSTQ(MI, FrameReg, Offset, FIOperandNum);
    return;
  case VE::LDQrii:
    processLDQ(MI, FrameReg, Offset, FIOperandNum);
    return;
  case VE::STVMrii:
    processSTVM(MI, FrameReg, Offset, FIOperandNum);
    return;
  case VE::LDVMrii:
    processLDVM(MI, FrameReg, Offset, FIOperandNum);
    return;
  case VE::STVM512rii:
    processSTVM512(MI, FrameReg, Offset, FIOperandNum);
    return;
  case VE::LDVM512rii:
    processLDVM512(MI, FrameReg, Offset, FIOperandNum);
    return;
  }
  prepareReplaceFI(MI, FrameReg, Offset);
  replaceFI(MI, FrameReg, Offset, FIOperandNum);
}

bool VERegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
                                         int SPAdj, unsigned FIOperandNum,
                                         RegScavenger *RS) const {
  assert(SPAdj == 0 && "Unexpected");

  MachineInstr &MI = *II;
  int FrameIndex = MI.getOperand(FIOperandNum).getIndex();

  MachineFunction &MF = *MI.getParent()->getParent();
  const VESubtarget &Subtarget = MF.getSubtarget<VESubtarget>();
  const VEFrameLowering &TFI = *getFrameLowering(MF);
  const TargetInstrInfo &TII = *Subtarget.getInstrInfo();
  const VERegisterInfo &TRI = *Subtarget.getRegisterInfo();
  DebugLoc DL = MI.getDebugLoc();
  EliminateFrameIndex EFI(TII, TRI, DL, *MI.getParent(), II);

  // Retrieve FrameReg and byte offset for stack slot.
  Register FrameReg;
  int64_t Offset =
      TFI.getFrameIndexReference(MF, FrameIndex, FrameReg).getFixed();
  Offset += MI.getOperand(FIOperandNum + offsetToDisp(MI)).getImm();

  EFI.processMI(MI, FrameReg, Offset, FIOperandNum);
  return false;
}

Register VERegisterInfo::getFrameRegister(const MachineFunction &MF) const {
  return VE::SX9;
}
