//===- PPCRegisterBankInfo.cpp --------------------------------------------===//
//
// 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
/// PowerPC.
//===----------------------------------------------------------------------===//

#include "PPCRegisterBankInfo.h"
#include "PPCRegisterInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/Support/Debug.h"

#define DEBUG_TYPE "ppc-reg-bank-info"

#define GET_TARGET_REGBANK_IMPL
#include "PPCGenRegisterBank.inc"

// This file will be TableGen'ed at some point.
#include "PPCGenRegisterBankInfo.def"

using namespace llvm;

PPCRegisterBankInfo::PPCRegisterBankInfo(const TargetRegisterInfo &TRI) {}

const RegisterBank &
PPCRegisterBankInfo::getRegBankFromRegClass(const TargetRegisterClass &RC,
                                            LLT Ty) const {
  switch (RC.getID()) {
  case PPC::G8RCRegClassID:
  case PPC::G8RC_NOX0RegClassID:
  case PPC::G8RC_and_G8RC_NOX0RegClassID:
  case PPC::GPRCRegClassID:
  case PPC::GPRC_NOR0RegClassID:
  case PPC::GPRC_and_GPRC_NOR0RegClassID:
    return getRegBank(PPC::GPRRegBankID);
  case PPC::VSFRCRegClassID:
  case PPC::SPILLTOVSRRC_and_VSFRCRegClassID:
  case PPC::SPILLTOVSRRC_and_VFRCRegClassID:
  case PPC::SPILLTOVSRRC_and_F4RCRegClassID:
  case PPC::F8RCRegClassID:
  case PPC::VFRCRegClassID:
  case PPC::VSSRCRegClassID:
  case PPC::F4RCRegClassID:
    return getRegBank(PPC::FPRRegBankID);
  case PPC::CRRCRegClassID:
  case PPC::CRBITRCRegClassID:
    return getRegBank(PPC::CRRegBankID);
  default:
    llvm_unreachable("Unexpected register class");
  }
}

const RegisterBankInfo::InstructionMapping &
PPCRegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
  const 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 RegisterBankInfo::InstructionMapping &Mapping =
        getInstrMappingImpl(MI);
    if (Mapping.isValid())
      return Mapping;
  }

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

  unsigned NumOperands = MI.getNumOperands();
  const ValueMapping *OperandsMapping = nullptr;
  unsigned Cost = 1;
  unsigned MappingID = DefaultMappingID;

  switch (Opc) {
    // Arithmetic ops.
  case TargetOpcode::G_ADD:
  case TargetOpcode::G_SUB:
    // Bitwise ops.
  case TargetOpcode::G_AND:
  case TargetOpcode::G_OR:
  case TargetOpcode::G_XOR:
    // Extension ops.
  case TargetOpcode::G_SEXT:
  case TargetOpcode::G_ZEXT:
  case TargetOpcode::G_ANYEXT:
    assert(NumOperands <= 3 &&
           "This code is for instructions with 3 or less operands");
    OperandsMapping = getValueMapping(PMI_GPR64);
    break;
  case TargetOpcode::G_FADD:
  case TargetOpcode::G_FSUB:
  case TargetOpcode::G_FMUL:
  case TargetOpcode::G_FDIV: {
    Register SrcReg = MI.getOperand(1).getReg();
    unsigned Size = getSizeInBits(SrcReg, MRI, TRI);

    assert((Size == 32 || Size == 64) && "Unsupported floating point types!\n");
    OperandsMapping = getValueMapping(Size == 32 ? PMI_FPR32 : PMI_FPR64);
    break;
  }
  case TargetOpcode::G_FCMP: {
    unsigned CmpSize = MRI.getType(MI.getOperand(2).getReg()).getSizeInBits();

    OperandsMapping = getOperandsMapping(
        {getValueMapping(PMI_CR), nullptr,
         getValueMapping(CmpSize == 32 ? PMI_FPR32 : PMI_FPR64),
         getValueMapping(CmpSize == 32 ? PMI_FPR32 : PMI_FPR64)});
    break;
  }
  case TargetOpcode::G_CONSTANT:
    OperandsMapping = getOperandsMapping({getValueMapping(PMI_GPR64), nullptr});
    break;
  case TargetOpcode::G_FPTOUI:
  case TargetOpcode::G_FPTOSI: {
    Register SrcReg = MI.getOperand(1).getReg();
    unsigned Size = getSizeInBits(SrcReg, MRI, TRI);

    OperandsMapping = getOperandsMapping(
        {getValueMapping(PMI_GPR64),
         getValueMapping(Size == 32 ? PMI_FPR32 : PMI_FPR64)});
    break;
  }
  case TargetOpcode::G_UITOFP:
  case TargetOpcode::G_SITOFP: {
    Register SrcReg = MI.getOperand(0).getReg();
    unsigned Size = getSizeInBits(SrcReg, MRI, TRI);

    OperandsMapping =
        getOperandsMapping({getValueMapping(Size == 32 ? PMI_FPR32 : PMI_FPR64),
                            getValueMapping(PMI_GPR64)});
    break;
  }
  case TargetOpcode::G_LOAD: {
    unsigned Size = MRI.getType(MI.getOperand(0).getReg()).getSizeInBits();
    // Check if that load feeds fp instructions.
    if (any_of(MRI.use_nodbg_instructions(MI.getOperand(0).getReg()),
               [&](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.
                 //
                 // Int->FP conversion operations are also captured in
                 // onlyDefinesFP().
                 return onlyUsesFP(UseMI, MRI, TRI);
               }))
      OperandsMapping = getOperandsMapping(
          {getValueMapping(Size == 64 ? PMI_FPR64 : PMI_FPR32),
           getValueMapping(PMI_GPR64)});
    else
      OperandsMapping = getOperandsMapping(
          {getValueMapping(Size == 64 ? PMI_GPR64 : PMI_GPR32),
           getValueMapping(PMI_GPR64)});
    break;
  }
  case TargetOpcode::G_STORE: {
    // Check if the store is fed by fp instructions.
    MachineInstr *DefMI = MRI.getVRegDef(MI.getOperand(0).getReg());
    unsigned Size = MRI.getType(MI.getOperand(0).getReg()).getSizeInBits();
    if (onlyDefinesFP(*DefMI, MRI, TRI))
      OperandsMapping = getOperandsMapping(
          {getValueMapping(Size == 64 ? PMI_FPR64 : PMI_FPR32),
           getValueMapping(PMI_GPR64)});
    else
      OperandsMapping = getOperandsMapping(
          {getValueMapping(Size == 64 ? PMI_GPR64 : PMI_GPR32),
           getValueMapping(PMI_GPR64)});
    break;
  }
  case TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS: {
    // FIXME: We have to check every operand in this MI and compute value
    // mapping accordingly.
    SmallVector<const ValueMapping *, 8> OpdsMapping(NumOperands);
    OperandsMapping = getOperandsMapping(OpdsMapping);
    break;
  }
  default:
    return getInvalidInstructionMapping();
  }

  return getInstructionMapping(MappingID, Cost, OperandsMapping, NumOperands);
}

/// Returns whether opcode \p Opc is a pre-isel generic floating-point opcode,
/// having only floating-point operands.
/// FIXME: this is copied from target AArch64. Needs some code refactor here to
/// put this function in GlobalISel/Utils.cpp.
static bool isPreISelGenericFloatingPointOpcode(unsigned Opc) {
  switch (Opc) {
  case TargetOpcode::G_FADD:
  case TargetOpcode::G_FSUB:
  case TargetOpcode::G_FMUL:
  case TargetOpcode::G_FMA:
  case TargetOpcode::G_FDIV:
  case TargetOpcode::G_FCONSTANT:
  case TargetOpcode::G_FPEXT:
  case TargetOpcode::G_FPTRUNC:
  case TargetOpcode::G_FCEIL:
  case TargetOpcode::G_FFLOOR:
  case TargetOpcode::G_FNEARBYINT:
  case TargetOpcode::G_FNEG:
  case TargetOpcode::G_FCOS:
  case TargetOpcode::G_FSIN:
  case TargetOpcode::G_FLOG10:
  case TargetOpcode::G_FLOG:
  case TargetOpcode::G_FLOG2:
  case TargetOpcode::G_FSQRT:
  case TargetOpcode::G_FABS:
  case TargetOpcode::G_FEXP:
  case TargetOpcode::G_FRINT:
  case TargetOpcode::G_INTRINSIC_TRUNC:
  case TargetOpcode::G_INTRINSIC_ROUND:
  case TargetOpcode::G_FMAXNUM:
  case TargetOpcode::G_FMINNUM:
  case TargetOpcode::G_FMAXIMUM:
  case TargetOpcode::G_FMINIMUM:
    return true;
  }
  return false;
}

/// \returns true if a given intrinsic \p ID only uses and defines FPRs.
static bool isFPIntrinsic(unsigned ID) {
  // TODO: Add more intrinsics.
  return false;
}

/// FIXME: this is copied from target AArch64. Needs some code refactor here to
/// put this function in class RegisterBankInfo.
bool PPCRegisterBankInfo::hasFPConstraints(const MachineInstr &MI,
                                           const MachineRegisterInfo &MRI,
                                           const TargetRegisterInfo &TRI,
                                           unsigned Depth) const {
  unsigned Op = MI.getOpcode();
  if (Op == TargetOpcode::G_INTRINSIC && isFPIntrinsic(MI.getIntrinsicID()))
    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 == &PPC::FPRRegBank)
    return true;
  if (RB == &PPC::GPRRegBank)
    return false;

  // We don't know anything.
  //
  // If we have a phi, we may be able to infer that it will be assigned a FPR
  // 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);
  });
}

/// FIXME: this is copied from target AArch64. Needs some code refactor here to
/// put this function in class RegisterBankInfo.
bool PPCRegisterBankInfo::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 TargetOpcode::G_LROUND:
  case TargetOpcode::G_LLROUND:
    return true;
  default:
    break;
  }
  return hasFPConstraints(MI, MRI, TRI, Depth);
}

/// FIXME: this is copied from target AArch64. Needs some code refactor here to
/// put this function in class RegisterBankInfo.
bool PPCRegisterBankInfo::onlyDefinesFP(const MachineInstr &MI,
                                        const MachineRegisterInfo &MRI,
                                        const TargetRegisterInfo &TRI,
                                        unsigned Depth) const {
  switch (MI.getOpcode()) {
  case TargetOpcode::G_SITOFP:
  case TargetOpcode::G_UITOFP:
    return true;
  default:
    break;
  }
  return hasFPConstraints(MI, MRI, TRI, Depth);
}

RegisterBankInfo::InstructionMappings
PPCRegisterBankInfo::getInstrAlternativeMappings(const MachineInstr &MI) const {
  // TODO Implement.
  return RegisterBankInfo::getInstrAlternativeMappings(MI);
}
