//===-- VEISelDAGToDAG.cpp - A dag to dag inst selector for VE ------------===//
//
// 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 defines an instruction selector for the VE target.
//
//===----------------------------------------------------------------------===//

#include "VE.h"
#include "VESelectionDAGInfo.h"
#include "VETargetMachine.h"
#include "llvm/CodeGen/SelectionDAGISel.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;

#define DEBUG_TYPE "ve-isel"
#define PASS_NAME "VE DAG->DAG Pattern Instruction Selection"

//===--------------------------------------------------------------------===//
/// VEDAGToDAGISel - VE specific code to select VE machine
/// instructions for SelectionDAG operations.
///
namespace {
class VEDAGToDAGISel : public SelectionDAGISel {
  /// Subtarget - Keep a pointer to the VE Subtarget around so that we can
  /// make the right decision when generating code for different targets.
  const VESubtarget *Subtarget;

public:
  VEDAGToDAGISel() = delete;

  explicit VEDAGToDAGISel(VETargetMachine &tm) : SelectionDAGISel(tm) {}

  bool runOnMachineFunction(MachineFunction &MF) override {
    Subtarget = &MF.getSubtarget<VESubtarget>();
    return SelectionDAGISel::runOnMachineFunction(MF);
  }

  void Select(SDNode *N) override;

  // Complex Pattern Selectors.
  bool selectADDRrri(SDValue N, SDValue &Base, SDValue &Index, SDValue &Offset);
  bool selectADDRrii(SDValue N, SDValue &Base, SDValue &Index, SDValue &Offset);
  bool selectADDRzri(SDValue N, SDValue &Base, SDValue &Index, SDValue &Offset);
  bool selectADDRzii(SDValue N, SDValue &Base, SDValue &Index, SDValue &Offset);
  bool selectADDRri(SDValue N, SDValue &Base, SDValue &Offset);
  bool selectADDRzi(SDValue N, SDValue &Base, SDValue &Offset);

  /// SelectInlineAsmMemoryOperand - Implement addressing mode selection for
  /// inline asm expressions.
  bool SelectInlineAsmMemoryOperand(const SDValue &Op,
                                    InlineAsm::ConstraintCode ConstraintID,
                                    std::vector<SDValue> &OutOps) override;

  // Include the pieces autogenerated from the target description.
#include "VEGenDAGISel.inc"

private:
  SDNode *getGlobalBaseReg();

  bool matchADDRrr(SDValue N, SDValue &Base, SDValue &Index);
  bool matchADDRri(SDValue N, SDValue &Base, SDValue &Offset);
};

class VEDAGToDAGISelLegacy : public SelectionDAGISelLegacy {
public:
  static char ID;
  explicit VEDAGToDAGISelLegacy(VETargetMachine &tm)
      : SelectionDAGISelLegacy(ID, std::make_unique<VEDAGToDAGISel>(tm)) {}
};
} // end anonymous namespace

char VEDAGToDAGISelLegacy::ID = 0;

INITIALIZE_PASS(VEDAGToDAGISelLegacy, DEBUG_TYPE, PASS_NAME, false, false)

bool VEDAGToDAGISel::selectADDRrri(SDValue Addr, SDValue &Base, SDValue &Index,
                                   SDValue &Offset) {
  if (Addr.getOpcode() == ISD::FrameIndex)
    return false;
  if (Addr.getOpcode() == ISD::TargetExternalSymbol ||
      Addr.getOpcode() == ISD::TargetGlobalAddress ||
      Addr.getOpcode() == ISD::TargetGlobalTLSAddress)
    return false; // direct calls.

  SDValue LHS, RHS;
  if (matchADDRri(Addr, LHS, RHS)) {
    if (matchADDRrr(LHS, Base, Index)) {
      Offset = RHS;
      return true;
    }
    // Return false to try selectADDRrii.
    return false;
  }
  if (matchADDRrr(Addr, LHS, RHS)) {
    // If the input is a pair of a frame-index and a register, move a
    // frame-index to LHS.  This generates MI with following operands.
    //    %dest, #FI, %reg, offset
    // In the eliminateFrameIndex, above MI is converted to the following.
    //    %dest, %fp, %reg, fi_offset + offset
    if (isa<FrameIndexSDNode>(RHS))
      std::swap(LHS, RHS);

    if (matchADDRri(RHS, Index, Offset)) {
      Base = LHS;
      return true;
    }
    if (matchADDRri(LHS, Base, Offset)) {
      Index = RHS;
      return true;
    }
    Base = LHS;
    Index = RHS;
    Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
    return true;
  }
  return false; // Let the reg+imm(=0) pattern catch this!
}

bool VEDAGToDAGISel::selectADDRrii(SDValue Addr, SDValue &Base, SDValue &Index,
                                   SDValue &Offset) {
  if (matchADDRri(Addr, Base, Offset)) {
    Index = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
    return true;
  }

  Base = Addr;
  Index = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
  Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
  return true;
}

bool VEDAGToDAGISel::selectADDRzri(SDValue Addr, SDValue &Base, SDValue &Index,
                                   SDValue &Offset) {
  // Prefer ADDRrii.
  return false;
}

bool VEDAGToDAGISel::selectADDRzii(SDValue Addr, SDValue &Base, SDValue &Index,
                                   SDValue &Offset) {
  if (isa<FrameIndexSDNode>(Addr))
    return false;
  if (Addr.getOpcode() == ISD::TargetExternalSymbol ||
      Addr.getOpcode() == ISD::TargetGlobalAddress ||
      Addr.getOpcode() == ISD::TargetGlobalTLSAddress)
    return false; // direct calls.

  if (auto *CN = dyn_cast<ConstantSDNode>(Addr)) {
    if (isInt<32>(CN->getSExtValue())) {
      Base = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
      Index = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
      Offset =
          CurDAG->getTargetConstant(CN->getZExtValue(), SDLoc(Addr), MVT::i32);
      return true;
    }
  }
  return false;
}

bool VEDAGToDAGISel::selectADDRri(SDValue Addr, SDValue &Base,
                                  SDValue &Offset) {
  if (matchADDRri(Addr, Base, Offset))
    return true;

  Base = Addr;
  Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
  return true;
}

bool VEDAGToDAGISel::selectADDRzi(SDValue Addr, SDValue &Base,
                                  SDValue &Offset) {
  if (isa<FrameIndexSDNode>(Addr))
    return false;
  if (Addr.getOpcode() == ISD::TargetExternalSymbol ||
      Addr.getOpcode() == ISD::TargetGlobalAddress ||
      Addr.getOpcode() == ISD::TargetGlobalTLSAddress)
    return false; // direct calls.

  if (auto *CN = dyn_cast<ConstantSDNode>(Addr)) {
    if (isInt<32>(CN->getSExtValue())) {
      Base = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
      Offset =
          CurDAG->getTargetConstant(CN->getZExtValue(), SDLoc(Addr), MVT::i32);
      return true;
    }
  }
  return false;
}

bool VEDAGToDAGISel::matchADDRrr(SDValue Addr, SDValue &Base, SDValue &Index) {
  if (isa<FrameIndexSDNode>(Addr))
    return false;
  if (Addr.getOpcode() == ISD::TargetExternalSymbol ||
      Addr.getOpcode() == ISD::TargetGlobalAddress ||
      Addr.getOpcode() == ISD::TargetGlobalTLSAddress)
    return false; // direct calls.

  if (Addr.getOpcode() == ISD::ADD) {
    ; // Nothing to do here.
  } else if (Addr.getOpcode() == ISD::OR) {
    // We want to look through a transform in InstCombine and DAGCombiner that
    // turns 'add' into 'or', so we can treat this 'or' exactly like an 'add'.
    if (!CurDAG->haveNoCommonBitsSet(Addr.getOperand(0), Addr.getOperand(1)))
      return false;
  } else {
    return false;
  }

  if (Addr.getOperand(0).getOpcode() == VEISD::Lo ||
      Addr.getOperand(1).getOpcode() == VEISD::Lo)
    return false; // Let the LEASL patterns catch this!

  Base = Addr.getOperand(0);
  Index = Addr.getOperand(1);
  return true;
}

bool VEDAGToDAGISel::matchADDRri(SDValue Addr, SDValue &Base, SDValue &Offset) {
  auto AddrTy = Addr->getValueType(0);
  if (FrameIndexSDNode *FIN = dyn_cast<FrameIndexSDNode>(Addr)) {
    Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), AddrTy);
    Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
    return true;
  }
  if (Addr.getOpcode() == ISD::TargetExternalSymbol ||
      Addr.getOpcode() == ISD::TargetGlobalAddress ||
      Addr.getOpcode() == ISD::TargetGlobalTLSAddress)
    return false; // direct calls.

  if (CurDAG->isBaseWithConstantOffset(Addr)) {
    ConstantSDNode *CN = cast<ConstantSDNode>(Addr.getOperand(1));
    if (isInt<32>(CN->getSExtValue())) {
      if (FrameIndexSDNode *FIN =
              dyn_cast<FrameIndexSDNode>(Addr.getOperand(0))) {
        // Constant offset from frame ref.
        Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), AddrTy);
      } else {
        Base = Addr.getOperand(0);
      }
      Offset =
          CurDAG->getTargetConstant(CN->getZExtValue(), SDLoc(Addr), MVT::i32);
      return true;
    }
  }
  return false;
}

void VEDAGToDAGISel::Select(SDNode *N) {
  if (N->isMachineOpcode()) {
    N->setNodeId(-1);
    return; // Already selected.
  }

  switch (N->getOpcode()) {

  // Late eliminate the LEGALAVL wrapper
  case VEISD::LEGALAVL:
    ReplaceNode(N, N->getOperand(0).getNode());
    return;

  // Lower (broadcast 1) and (broadcast 0) to VM[P]0
  case VEISD::VEC_BROADCAST: {
    MVT SplatResTy = N->getSimpleValueType(0);
    if (SplatResTy.getVectorElementType() != MVT::i1)
      break;

    // Constant non-zero broadcast.
    auto BConst = dyn_cast<ConstantSDNode>(N->getOperand(0));
    if (!BConst)
      break;
    bool BCTrueMask = (BConst->getSExtValue() != 0);
    if (!BCTrueMask)
      break;

    // Packed or non-packed.
    SDValue New;
    if (SplatResTy.getVectorNumElements() == StandardVectorWidth) {
      New = CurDAG->getCopyFromReg(CurDAG->getEntryNode(), SDLoc(N), VE::VM0,
                                   MVT::v256i1);
    } else if (SplatResTy.getVectorNumElements() == PackedVectorWidth) {
      New = CurDAG->getCopyFromReg(CurDAG->getEntryNode(), SDLoc(N), VE::VMP0,
                                   MVT::v512i1);
    } else
      break;

    // Replace.
    ReplaceNode(N, New.getNode());
    return;
  }

  case VEISD::GLOBAL_BASE_REG:
    ReplaceNode(N, getGlobalBaseReg());
    return;
  }

  SelectCode(N);
}

/// SelectInlineAsmMemoryOperand - Implement addressing mode selection for
/// inline asm expressions.
bool VEDAGToDAGISel::SelectInlineAsmMemoryOperand(
    const SDValue &Op, InlineAsm::ConstraintCode ConstraintID,
    std::vector<SDValue> &OutOps) {
  SDValue Op0, Op1;
  switch (ConstraintID) {
  default:
    llvm_unreachable("Unexpected asm memory constraint");
  case InlineAsm::ConstraintCode::o:
  case InlineAsm::ConstraintCode::m: // memory
    // Try to match ADDRri since reg+imm style is safe for all VE instructions
    // with a memory operand.
    if (selectADDRri(Op, Op0, Op1)) {
      OutOps.push_back(Op0);
      OutOps.push_back(Op1);
      return false;
    }
    // Otherwise, require the address to be in a register and immediate 0.
    OutOps.push_back(Op);
    OutOps.push_back(CurDAG->getTargetConstant(0, SDLoc(Op), MVT::i32));
    return false;
  }
  return true;
}

SDNode *VEDAGToDAGISel::getGlobalBaseReg() {
  Register GlobalBaseReg = Subtarget->getInstrInfo()->getGlobalBaseReg(MF);
  return CurDAG
      ->getRegister(GlobalBaseReg, TLI->getPointerTy(CurDAG->getDataLayout()))
      .getNode();
}

/// createVEISelDag - This pass converts a legalized DAG into a
/// VE-specific DAG, ready for instruction scheduling.
///
FunctionPass *llvm::createVEISelDag(VETargetMachine &TM) {
  return new VEDAGToDAGISelLegacy(TM);
}
