//===- X86RegisterBankInfo.cpp -----------------------------------*- 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 implements the targeting of the RegisterBankInfo class for X86.
/// \todo This should be generated by TableGen.
//===----------------------------------------------------------------------===//

#include "X86RegisterBankInfo.h"
#include "X86InstrInfo.h"
#include "X86Subtarget.h"
#include "llvm/CodeGen/GlobalISel/GenericMachineInstrs.h"
#include "llvm/CodeGen/GlobalISel/Utils.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/RegisterBank.h"
#include "llvm/CodeGen/RegisterBankInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/IR/IntrinsicsX86.h"

#define GET_TARGET_REGBANK_IMPL
#include "X86GenRegisterBank.inc"

using namespace llvm;
// This file will be TableGen'ed at some point.
#define GET_TARGET_REGBANK_INFO_IMPL
#include "X86GenRegisterBankInfo.def"

X86RegisterBankInfo::X86RegisterBankInfo(const TargetRegisterInfo &TRI) {

  // validate RegBank initialization.
  const RegisterBank &RBGPR = getRegBank(X86::GPRRegBankID);
  (void)RBGPR;
  assert(&X86::GPRRegBank == &RBGPR && "Incorrect RegBanks inizalization.");

  // The GPR register bank is fully defined by all the registers in
  // GR64 + its subclasses.
  assert(RBGPR.covers(*TRI.getRegClass(X86::GR64RegClassID)) &&
         "Subclass not added?");
  assert(getMaximumSize(RBGPR.getID()) == 64 &&
         "GPRs should hold up to 64-bit");
}

// \returns true if a given intrinsic only uses and defines FPRs.
static bool isFPIntrinsic(const MachineRegisterInfo &MRI,
                          const MachineInstr &MI) {
  // TODO: Add more intrinsics.
  switch (cast<GIntrinsic>(MI).getIntrinsicID()) {
  default:
    return false;
  // SSE1
  case Intrinsic::x86_sse_rcp_ss:
  case Intrinsic::x86_sse_rcp_ps:
  case Intrinsic::x86_sse_rsqrt_ss:
  case Intrinsic::x86_sse_rsqrt_ps:
  case Intrinsic::x86_sse_min_ss:
  case Intrinsic::x86_sse_min_ps:
  case Intrinsic::x86_sse_max_ss:
  case Intrinsic::x86_sse_max_ps:
    return true;
  }
  return false;
}

bool X86RegisterBankInfo::hasFPConstraints(const MachineInstr &MI,
                                           const MachineRegisterInfo &MRI,
                                           const TargetRegisterInfo &TRI,
                                           unsigned Depth) const {
  unsigned Op = MI.getOpcode();
  if (Op == TargetOpcode::G_INTRINSIC && isFPIntrinsic(MRI, MI))
    return true;

  // Do we have an explicit floating point instruction?
  if (isPreISelGenericFloatingPointOpcode(Op))
    return true;

  // No. Check if we have a copy-like instruction. If we do, then we could
  // still be fed by floating point instructions.
  if (Op != TargetOpcode::COPY && !MI.isPHI() &&
      !isPreISelGenericOptimizationHint(Op))
    return false;

  // Check if we already know the register bank.
  auto *RB = getRegBank(MI.getOperand(0).getReg(), MRI, TRI);
  if (RB == &getRegBank(X86::PSRRegBankID))
    return true;
  if (RB == &getRegBank(X86::GPRRegBankID))
    return false;

  // We don't know anything.
  //
  // If we have a phi, we may be able to infer that it will be assigned a fp
  // type based off of its inputs.
  if (!MI.isPHI() || Depth > MaxFPRSearchDepth)
    return false;

  return any_of(MI.explicit_uses(), [&](const MachineOperand &Op) {
    return Op.isReg() &&
           onlyDefinesFP(*MRI.getVRegDef(Op.getReg()), MRI, TRI, Depth + 1);
  });
}

bool X86RegisterBankInfo::onlyUsesFP(const MachineInstr &MI,
                                     const MachineRegisterInfo &MRI,
                                     const TargetRegisterInfo &TRI,
                                     unsigned Depth) const {
  switch (MI.getOpcode()) {
  case TargetOpcode::G_FPTOSI:
  case TargetOpcode::G_FPTOUI:
  case TargetOpcode::G_FCMP:
  case X86::G_FIST:
  case TargetOpcode::G_LROUND:
  case TargetOpcode::G_LLROUND:
  case TargetOpcode::G_INTRINSIC_TRUNC:
  case TargetOpcode::G_INTRINSIC_ROUND:
    return true;
  default:
    break;
  }
  return hasFPConstraints(MI, MRI, TRI, Depth);
}

bool X86RegisterBankInfo::onlyDefinesFP(const MachineInstr &MI,
                                        const MachineRegisterInfo &MRI,
                                        const TargetRegisterInfo &TRI,
                                        unsigned Depth) const {
  switch (MI.getOpcode()) {
  case TargetOpcode::G_SITOFP:
  case TargetOpcode::G_UITOFP:
  case X86::G_FILD:
    return true;
  default:
    break;
  }
  return hasFPConstraints(MI, MRI, TRI, Depth);
}

X86GenRegisterBankInfo::PartialMappingIdx
X86GenRegisterBankInfo::getPartialMappingIdx(const MachineInstr &MI,
                                             const LLT &Ty, bool isFP) {
  const MachineFunction *MF = MI.getMF();
  const X86Subtarget *ST = &MF->getSubtarget<X86Subtarget>();
  bool HasSSE1 = ST->hasSSE1();
  bool HasSSE2 = ST->hasSSE2();
  // 80 bits is only generated for X87 floating points.
  if (Ty.getSizeInBits() == 80)
    isFP = true;
  if ((Ty.isScalar() && !isFP) || Ty.isPointer()) {
    switch (Ty.getSizeInBits()) {
    case 1:
    case 8:
      return PMI_GPR8;
    case 16:
      return PMI_GPR16;
    case 32:
      return PMI_GPR32;
    case 64:
      return PMI_GPR64;
    case 128:
      return PMI_VEC128;
      break;
    default:
      llvm_unreachable("Unsupported register size.");
    }
  } else if (Ty.isScalar()) {
    switch (Ty.getSizeInBits()) {
    case 32:
      return HasSSE1 ? PMI_FP32 : PMI_PSR32;
    case 64:
      return HasSSE2 ? PMI_FP64 : PMI_PSR64;
    case 128:
      return PMI_VEC128;
    case 80:
      return PMI_PSR80;
    default:
      llvm_unreachable("Unsupported register size.");
    }
  } else {
    switch (Ty.getSizeInBits()) {
    case 128:
      return PMI_VEC128;
    case 256:
      return PMI_VEC256;
    case 512:
      return PMI_VEC512;
    default:
      llvm_unreachable("Unsupported register size.");
    }
  }

  return PMI_None;
}

void X86RegisterBankInfo::getInstrPartialMappingIdxs(
    const MachineInstr &MI, const MachineRegisterInfo &MRI, const bool isFP,
    SmallVectorImpl<PartialMappingIdx> &OpRegBankIdx) {

  unsigned NumOperands = MI.getNumOperands();
  for (unsigned Idx = 0; Idx < NumOperands; ++Idx) {
    auto &MO = MI.getOperand(Idx);
    if (!MO.isReg() || !MO.getReg())
      OpRegBankIdx[Idx] = PMI_None;
    else
      OpRegBankIdx[Idx] =
          getPartialMappingIdx(MI, MRI.getType(MO.getReg()), isFP);
  }
}

bool X86RegisterBankInfo::getInstrValueMapping(
    const MachineInstr &MI,
    const SmallVectorImpl<PartialMappingIdx> &OpRegBankIdx,
    SmallVectorImpl<const ValueMapping *> &OpdsMapping) {

  unsigned NumOperands = MI.getNumOperands();
  for (unsigned Idx = 0; Idx < NumOperands; ++Idx) {
    if (!MI.getOperand(Idx).isReg())
      continue;
    if (!MI.getOperand(Idx).getReg())
      continue;

    auto Mapping = getValueMapping(OpRegBankIdx[Idx], 1);
    if (!Mapping->isValid())
      return false;

    OpdsMapping[Idx] = Mapping;
  }
  return true;
}

const RegisterBankInfo::InstructionMapping &
X86RegisterBankInfo::getSameOperandsMapping(const MachineInstr &MI,
                                            bool isFP) const {
  const MachineFunction &MF = *MI.getParent()->getParent();
  const MachineRegisterInfo &MRI = MF.getRegInfo();

  unsigned NumOperands = MI.getNumOperands();
  LLT Ty = MRI.getType(MI.getOperand(0).getReg());

  if (NumOperands != 3 || (Ty != MRI.getType(MI.getOperand(1).getReg())) ||
      (Ty != MRI.getType(MI.getOperand(2).getReg())))
    llvm_unreachable("Unsupported operand mapping yet.");

  auto Mapping = getValueMapping(getPartialMappingIdx(MI, Ty, isFP), 3);
  return getInstructionMapping(DefaultMappingID, 1, Mapping, NumOperands);
}

const RegisterBankInfo::InstructionMapping &
X86RegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
  const MachineFunction &MF = *MI.getParent()->getParent();
  const TargetSubtargetInfo &STI = MF.getSubtarget();
  const TargetRegisterInfo &TRI = *STI.getRegisterInfo();
  const MachineRegisterInfo &MRI = MF.getRegInfo();
  unsigned Opc = MI.getOpcode();

  // Try the default logic for non-generic instructions that are either
  // copies or already have some operands assigned to banks.
  if (!isPreISelGenericOpcode(Opc) || Opc == TargetOpcode::G_PHI) {
    const InstructionMapping &Mapping = getInstrMappingImpl(MI);
    if (Mapping.isValid())
      return Mapping;
  }

  switch (Opc) {
  case TargetOpcode::G_ADD:
  case TargetOpcode::G_SUB:
  case TargetOpcode::G_MUL:
    return getSameOperandsMapping(MI, false);
  case TargetOpcode::G_FADD:
  case TargetOpcode::G_FSUB:
  case TargetOpcode::G_FMUL:
  case TargetOpcode::G_FDIV:
    return getSameOperandsMapping(MI, true);
  case TargetOpcode::G_SHL:
  case TargetOpcode::G_LSHR:
  case TargetOpcode::G_ASHR: {
    unsigned NumOperands = MI.getNumOperands();
    LLT Ty = MRI.getType(MI.getOperand(0).getReg());

    auto Mapping = getValueMapping(getPartialMappingIdx(MI, Ty, false), 3);
    return getInstructionMapping(DefaultMappingID, 1, Mapping, NumOperands);
  }
  default:
    break;
  }

  unsigned NumOperands = MI.getNumOperands();
  SmallVector<PartialMappingIdx, 4> OpRegBankIdx(NumOperands);

  switch (Opc) {
  case TargetOpcode::G_FSQRT:
  case TargetOpcode::G_FPEXT:
  case TargetOpcode::G_FPTRUNC:
  case TargetOpcode::G_FCONSTANT:
    // Instruction having only floating-point operands (all scalars in
    // VECRReg)
    getInstrPartialMappingIdxs(MI, MRI, /* isFP= */ true, OpRegBankIdx);
    break;
  case X86::G_FIST:
  case X86::G_FILD: {
    auto &Op0 = MI.getOperand(0);
    auto &Op1 = MI.getOperand(1);
    const LLT Ty0 = MRI.getType(Op0.getReg());
    const LLT Ty1 = MRI.getType(Op1.getReg());
    OpRegBankIdx[0] = getPartialMappingIdx(MI, Ty0, /* isFP= */ true);
    OpRegBankIdx[1] = getPartialMappingIdx(MI, Ty1, /* isFP= */ false);
    break;
  }
  case TargetOpcode::G_SITOFP:
  case TargetOpcode::G_FPTOSI:
  case TargetOpcode::G_UITOFP:
  case TargetOpcode::G_FPTOUI: {
    // Some of the floating-point instructions have mixed GPR and FP
    // operands: fine-tune the computed mapping.
    auto &Op0 = MI.getOperand(0);
    auto &Op1 = MI.getOperand(1);
    const LLT Ty0 = MRI.getType(Op0.getReg());
    const LLT Ty1 = MRI.getType(Op1.getReg());

    bool FirstArgIsFP =
        Opc == TargetOpcode::G_SITOFP || Opc == TargetOpcode::G_UITOFP;
    OpRegBankIdx[0] = getPartialMappingIdx(MI, Ty0, /* isFP= */ FirstArgIsFP);
    OpRegBankIdx[1] = getPartialMappingIdx(MI, Ty1, /* isFP= */ !FirstArgIsFP);
    break;
  }
  case TargetOpcode::G_FCMP: {
    LLT Ty1 = MRI.getType(MI.getOperand(2).getReg());
    LLT Ty2 = MRI.getType(MI.getOperand(3).getReg());
    (void)Ty2;
    assert(Ty1.getSizeInBits() == Ty2.getSizeInBits() &&
           "Mismatched operand sizes for G_FCMP");

    unsigned Size = Ty1.getSizeInBits();
    (void)Size;
    assert((Size == 32 || Size == 64 || Size == 80) &&
           "Unsupported size for G_FCMP");
    auto FpRegBank = getPartialMappingIdx(MI, Ty1, /* isFP= */ true);
    OpRegBankIdx = {PMI_GPR8,
                    /* Predicate */ PMI_None, FpRegBank, FpRegBank};
    break;
  }
  case TargetOpcode::G_FABS:
  case TargetOpcode::G_TRUNC:
  case TargetOpcode::G_ANYEXT: {
    auto &Op0 = MI.getOperand(0);
    auto &Op1 = MI.getOperand(1);
    const LLT Ty0 = MRI.getType(Op0.getReg());
    const LLT Ty1 = MRI.getType(Op1.getReg());

    bool isFPTrunc = (Ty0.getSizeInBits() == 32 || Ty0.getSizeInBits() == 64) &&
                     Ty1.getSizeInBits() == 128 && Opc == TargetOpcode::G_TRUNC;
    bool isFPAnyExt =
        Ty0.getSizeInBits() == 128 &&
        (Ty1.getSizeInBits() == 32 || Ty1.getSizeInBits() == 64) &&
        Opc == TargetOpcode::G_ANYEXT;
    bool isFAbs = (Opc == TargetOpcode::G_FABS);
    getInstrPartialMappingIdxs(
        MI, MRI, /* isFP= */ isFPTrunc || isFPAnyExt || isFAbs, OpRegBankIdx);
    break;
  }
  case TargetOpcode::G_LOAD: {
    // Check if that load feeds fp instructions.
    // In that case, we want the default mapping to be on FPR
    // instead of blind map every scalar to GPR.
    bool IsFP = any_of(MRI.use_nodbg_instructions(cast<GLoad>(MI).getDstReg()),
                       [&](const MachineInstr &UseMI) {
                         // If we have at least one direct use in a FP
                         // instruction, assume this was a floating point load
                         // in the IR. If it was not, we would have had a
                         // bitcast before reaching that instruction.
                         return onlyUsesFP(UseMI, MRI, TRI);
                       });
    getInstrPartialMappingIdxs(MI, MRI, IsFP, OpRegBankIdx);
    break;
  }
  case TargetOpcode::G_STORE: {
    // Check if that store is fed by fp instructions.
    Register VReg = cast<GStore>(MI).getValueReg();
    if (!VReg)
      break;
    MachineInstr *DefMI = MRI.getVRegDef(VReg);
    bool IsFP = onlyDefinesFP(*DefMI, MRI, TRI);
    getInstrPartialMappingIdxs(MI, MRI, IsFP, OpRegBankIdx);
    break;
  }
  default:
    // Track the bank of each register, use NotFP mapping (all scalars in
    // GPRs)
    getInstrPartialMappingIdxs(MI, MRI, /* isFP= */ false, OpRegBankIdx);
    break;
  }

  // Finally construct the computed mapping.
  SmallVector<const ValueMapping *, 8> OpdsMapping(NumOperands);
  if (!getInstrValueMapping(MI, OpRegBankIdx, OpdsMapping))
    return getInvalidInstructionMapping();

  return getInstructionMapping(DefaultMappingID, /* Cost */ 1,
                               getOperandsMapping(OpdsMapping), NumOperands);
}

void X86RegisterBankInfo::applyMappingImpl(
    MachineIRBuilder &Builder, const OperandsMapper &OpdMapper) const {
  return applyDefaultMapping(OpdMapper);
}

RegisterBankInfo::InstructionMappings
X86RegisterBankInfo::getInstrAlternativeMappings(const MachineInstr &MI) const {

  const MachineFunction &MF = *MI.getParent()->getParent();
  const TargetSubtargetInfo &STI = MF.getSubtarget();
  const TargetRegisterInfo &TRI = *STI.getRegisterInfo();
  const MachineRegisterInfo &MRI = MF.getRegInfo();

  switch (MI.getOpcode()) {
  case TargetOpcode::G_LOAD:
  case TargetOpcode::G_STORE:
  case TargetOpcode::G_IMPLICIT_DEF: {
    // we going to try to map 32/64/80 bit to PMI_FP32/PMI_FP64/PMI_FP80
    unsigned Size = getSizeInBits(MI.getOperand(0).getReg(), MRI, TRI);
    if (Size != 32 && Size != 64 && Size != 80)
      break;

    unsigned NumOperands = MI.getNumOperands();

    // Track the bank of each register, use FP mapping (all scalars in VEC)
    SmallVector<PartialMappingIdx, 4> OpRegBankIdx(NumOperands);
    getInstrPartialMappingIdxs(MI, MRI, /* isFP= */ true, OpRegBankIdx);

    // Finally construct the computed mapping.
    SmallVector<const ValueMapping *, 8> OpdsMapping(NumOperands);
    if (!getInstrValueMapping(MI, OpRegBankIdx, OpdsMapping))
      break;

    const RegisterBankInfo::InstructionMapping &Mapping = getInstructionMapping(
        /*ID*/ 1, /*Cost*/ 1, getOperandsMapping(OpdsMapping), NumOperands);
    InstructionMappings AltMappings;
    AltMappings.push_back(&Mapping);
    return AltMappings;
  }
  default:
    break;
  }
  return RegisterBankInfo::getInstrAlternativeMappings(MI);
}
