//===- ARCISelDAGToDAG.cpp - ARC dag to dag inst selector -------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file defines an instruction selector for the ARC target.
//
//===----------------------------------------------------------------------===//

#include "ARC.h"
#include "ARCTargetMachine.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/SelectionDAG.h"
#include "llvm/CodeGen/SelectionDAGISel.h"
#include "llvm/CodeGen/TargetLowering.h"
#include "llvm/IR/CallingConv.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

#define DEBUG_TYPE "arc-isel"
#define PASS_NAME "ARC DAG->DAG Pattern Instruction Selection"

/// ARCDAGToDAGISel - ARC specific code to select ARC machine
/// instructions for SelectionDAG operations.
namespace {

class ARCDAGToDAGISel : public SelectionDAGISel {
public:
  ARCDAGToDAGISel() = delete;

  ARCDAGToDAGISel(ARCTargetMachine &TM, CodeGenOptLevel OptLevel)
      : SelectionDAGISel(TM, OptLevel) {}

  void Select(SDNode *N) override;

  // Complex Pattern Selectors.
  bool SelectFrameADDR_ri(SDValue Addr, SDValue &Base, SDValue &Offset);
  bool SelectAddrModeS9(SDValue Addr, SDValue &Base, SDValue &Offset);
  bool SelectAddrModeImm(SDValue Addr, SDValue &Base, SDValue &Offset);
  bool SelectAddrModeFar(SDValue Addr, SDValue &Base, SDValue &Offset);

// Include the pieces autogenerated from the target description.
#include "ARCGenDAGISel.inc"
};

class ARCDAGToDAGISelLegacy : public SelectionDAGISelLegacy {
public:
  static char ID;
  explicit ARCDAGToDAGISelLegacy(ARCTargetMachine &TM, CodeGenOptLevel OptLevel)
      : SelectionDAGISelLegacy(
            ID, std::make_unique<ARCDAGToDAGISel>(TM, OptLevel)) {}
};

char ARCDAGToDAGISelLegacy::ID;

} // end anonymous namespace

INITIALIZE_PASS(ARCDAGToDAGISelLegacy, DEBUG_TYPE, PASS_NAME, false, false)

/// This pass converts a legalized DAG into a ARC-specific DAG, ready for
/// instruction scheduling.
FunctionPass *llvm::createARCISelDag(ARCTargetMachine &TM,
                                     CodeGenOptLevel OptLevel) {
  return new ARCDAGToDAGISelLegacy(TM, OptLevel);
}

bool ARCDAGToDAGISel::SelectAddrModeImm(SDValue Addr, SDValue &Base,
                                        SDValue &Offset) {
  if (Addr.getOpcode() == ARCISD::GAWRAPPER) {
    Base = Addr.getOperand(0);
    Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
    return true;
  }
  return false;
}

bool ARCDAGToDAGISel::SelectAddrModeS9(SDValue Addr, SDValue &Base,
                                       SDValue &Offset) {
  if (Addr.getOpcode() == ARCISD::GAWRAPPER) {
    return false;
  }

  if (Addr.getOpcode() != ISD::ADD && Addr.getOpcode() != ISD::SUB &&
      !CurDAG->isBaseWithConstantOffset(Addr)) {
    if (Addr.getOpcode() == ISD::FrameIndex) {
      // Match frame index.
      int FI = cast<FrameIndexSDNode>(Addr)->getIndex();
      Base = CurDAG->getTargetFrameIndex(
          FI, TLI->getPointerTy(CurDAG->getDataLayout()));
    } else {
      Base = Addr;
    }
    Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
    return true;
  }

  if (ConstantSDNode *RHS = dyn_cast<ConstantSDNode>(Addr.getOperand(1))) {
    int32_t RHSC = RHS->getSExtValue();
    if (Addr.getOpcode() == ISD::SUB)
      RHSC = -RHSC;

    // Do we need more than 9 bits to encode?
    if (!isInt<9>(RHSC))
      return false;
    Base = Addr.getOperand(0);
    if (Base.getOpcode() == ISD::FrameIndex) {
      int FI = cast<FrameIndexSDNode>(Base)->getIndex();
      Base = CurDAG->getTargetFrameIndex(
          FI, TLI->getPointerTy(CurDAG->getDataLayout()));
    }
    Offset = CurDAG->getTargetConstant(RHSC, SDLoc(Addr), MVT::i32);
    return true;
  }
  Base = Addr;
  Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
  return true;
}

bool ARCDAGToDAGISel::SelectAddrModeFar(SDValue Addr, SDValue &Base,
                                        SDValue &Offset) {
  if (SelectAddrModeS9(Addr, Base, Offset))
    return false;
  if (Addr.getOpcode() == ARCISD::GAWRAPPER) {
    return false;
  }
  if (ConstantSDNode *RHS = dyn_cast<ConstantSDNode>(Addr.getOperand(1))) {
    int32_t RHSC = RHS->getSExtValue();
    if (Addr.getOpcode() == ISD::SUB)
      RHSC = -RHSC;
    Base = Addr.getOperand(0);
    Offset = CurDAG->getTargetConstant(RHSC, SDLoc(Addr), MVT::i32);
    return true;
  }
  return false;
}

// Is this a legal frame index addressing expression.
bool ARCDAGToDAGISel::SelectFrameADDR_ri(SDValue Addr, SDValue &Base,
                                         SDValue &Offset) {
  FrameIndexSDNode *FIN = nullptr;
  if ((FIN = dyn_cast<FrameIndexSDNode>(Addr))) {
    Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), MVT::i32);
    Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), MVT::i32);
    return true;
  }
  if (Addr.getOpcode() == ISD::ADD) {
    ConstantSDNode *CN = nullptr;
    if ((FIN = dyn_cast<FrameIndexSDNode>(Addr.getOperand(0))) &&
        (CN = dyn_cast<ConstantSDNode>(Addr.getOperand(1))) &&
        (CN->getSExtValue() % 4 == 0 && CN->getSExtValue() >= 0)) {
      // Constant positive word offset from frame index
      Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), MVT::i32);
      Offset =
          CurDAG->getTargetConstant(CN->getSExtValue(), SDLoc(Addr), MVT::i32);
      return true;
    }
  }
  return false;
}

void ARCDAGToDAGISel::Select(SDNode *N) {
  switch (N->getOpcode()) {
  case ISD::Constant: {
    uint64_t CVal = N->getAsZExtVal();
    ReplaceNode(N, CurDAG->getMachineNode(
                       isInt<12>(CVal) ? ARC::MOV_rs12 : ARC::MOV_rlimm,
                       SDLoc(N), MVT::i32,
                       CurDAG->getTargetConstant(CVal, SDLoc(N), MVT::i32)));
    return;
  }
  }
  SelectCode(N);
}
