//===- XtensaISelDAGToDAG.cpp - A dag to dag inst selector for Xtensa -----===//
//
// 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 Xtensa target.
//
//===----------------------------------------------------------------------===//

#include "Xtensa.h"
#include "XtensaTargetMachine.h"
#include "XtensaUtils.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/SelectionDAGISel.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

#define DEBUG_TYPE "xtensa-isel"

namespace {

class XtensaDAGToDAGISel : public SelectionDAGISel {
public:
  XtensaDAGToDAGISel(XtensaTargetMachine &TM, CodeGenOptLevel OptLevel)
      : SelectionDAGISel(TM, OptLevel) {}

  void Select(SDNode *Node) override;

  // For load/store instructions generate (base+offset) pair from
  // memory address. The offset must be a multiple of scale argument.
  bool selectMemRegAddr(SDValue Addr, SDValue &Base, SDValue &Offset,
                        int Scale) {
    EVT ValTy = Addr.getValueType();

    // if Address is FI, get the TargetFrameIndex.
    if (FrameIndexSDNode *FIN = dyn_cast<FrameIndexSDNode>(Addr)) {
      Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), ValTy);
      Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), ValTy);

      return true;
    }

    if (TM.isPositionIndependent()) {
      DiagnosticInfoUnsupported Diag(CurDAG->getMachineFunction().getFunction(),
                                     "PIC relocations are not supported ",
                                     Addr.getDebugLoc());
      CurDAG->getContext()->diagnose(Diag);
    }

    if ((Addr.getOpcode() == ISD::TargetExternalSymbol ||
         Addr.getOpcode() == ISD::TargetGlobalAddress))
      return false;

    // Addresses of the form FI+const
    bool Valid = false;
    if (CurDAG->isBaseWithConstantOffset(Addr)) {
      ConstantSDNode *CN = dyn_cast<ConstantSDNode>(Addr.getOperand(1));
      int64_t OffsetVal = CN->getSExtValue();

      Valid = isValidAddrOffset(Scale, OffsetVal);

      if (Valid) {
        // If the first operand is a FI, get the TargetFI Node
        if (FrameIndexSDNode *FIN =
                dyn_cast<FrameIndexSDNode>(Addr.getOperand(0)))
          Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), ValTy);
        else
          Base = Addr.getOperand(0);

        Offset =
            CurDAG->getTargetConstant(CN->getZExtValue(), SDLoc(Addr), ValTy);
        return true;
      }
    }

    // Last case
    Base = Addr;
    Offset = CurDAG->getTargetConstant(0, SDLoc(Addr), Addr.getValueType());
    return true;
  }

  bool selectMemRegAddrISH1(SDValue Addr, SDValue &Base, SDValue &Offset) {
    return selectMemRegAddr(Addr, Base, Offset, 1);
  }

  bool selectMemRegAddrISH2(SDValue Addr, SDValue &Base, SDValue &Offset) {
    return selectMemRegAddr(Addr, Base, Offset, 2);
  }

  bool selectMemRegAddrISH4(SDValue Addr, SDValue &Base, SDValue &Offset) {
    return selectMemRegAddr(Addr, Base, Offset, 4);
  }

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

class XtensaDAGToDAGISelLegacy : public SelectionDAGISelLegacy {
public:
  static char ID;

  XtensaDAGToDAGISelLegacy(XtensaTargetMachine &TM, CodeGenOptLevel OptLevel)
      : SelectionDAGISelLegacy(
            ID, std::make_unique<XtensaDAGToDAGISel>(TM, OptLevel)) {}

  StringRef getPassName() const override {
    return "Xtensa DAG->DAG Pattern Instruction Selection";
  }
};
} // end anonymous namespace

char XtensaDAGToDAGISelLegacy::ID = 0;

FunctionPass *llvm::createXtensaISelDag(XtensaTargetMachine &TM,
                                        CodeGenOptLevel OptLevel) {
  return new XtensaDAGToDAGISelLegacy(TM, OptLevel);
}

void XtensaDAGToDAGISel::Select(SDNode *Node) {
  SDLoc DL(Node);

  // If we have a custom node, we already have selected!
  if (Node->isMachineOpcode()) {
    Node->setNodeId(-1);
    return;
  }

  SelectCode(Node);
}
