//===-- PTXISelDAGToDAG.cpp - A dag to dag inst selector for PTX ----------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines an instruction selector for the PTX target.
//
//===----------------------------------------------------------------------===//

#include "PTX.h"
#include "PTXMachineFunctionInfo.h"
#include "PTXTargetMachine.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/CodeGen/SelectionDAGISel.h"
#include "llvm/DerivedTypes.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

namespace {
// PTXDAGToDAGISel - PTX specific code to select PTX machine
// instructions for SelectionDAG operations.
class PTXDAGToDAGISel : public SelectionDAGISel {
  public:
    PTXDAGToDAGISel(PTXTargetMachine &TM, CodeGenOpt::Level OptLevel);

    virtual const char *getPassName() const {
      return "PTX DAG->DAG Pattern Instruction Selection";
    }

    SDNode *Select(SDNode *Node);

    // Complex Pattern Selectors.
    bool SelectADDRrr(SDValue &Addr, SDValue &R1, SDValue &R2);
    bool SelectADDRri(SDValue &Addr, SDValue &Base, SDValue &Offset);
    bool SelectADDRii(SDValue &Addr, SDValue &Base, SDValue &Offset);
    bool SelectADDRlocal(SDValue &Addr, SDValue &Base, SDValue &Offset);

    // Include the pieces auto'gened from the target description
#include "PTXGenDAGISel.inc"

  private:
    // We need this only because we can't match intruction BRAdp
    // pattern (PTXbrcond bb:$d, ...) in PTXInstrInfo.td
    SDNode *SelectBRCOND(SDNode *Node);

    SDNode *SelectREADPARAM(SDNode *Node);
    SDNode *SelectWRITEPARAM(SDNode *Node);
    SDNode *SelectFrameIndex(SDNode *Node);

    bool isImm(const SDValue &operand);
    bool SelectImm(const SDValue &operand, SDValue &imm);

    const PTXSubtarget& getSubtarget() const;
}; // class PTXDAGToDAGISel
} // namespace

// createPTXISelDag - This pass converts a legalized DAG into a
// PTX-specific DAG, ready for instruction scheduling
FunctionPass *llvm::createPTXISelDag(PTXTargetMachine &TM,
                                     CodeGenOpt::Level OptLevel) {
  return new PTXDAGToDAGISel(TM, OptLevel);
}

PTXDAGToDAGISel::PTXDAGToDAGISel(PTXTargetMachine &TM,
                                 CodeGenOpt::Level OptLevel)
  : SelectionDAGISel(TM, OptLevel) {}

SDNode *PTXDAGToDAGISel::Select(SDNode *Node) {
  switch (Node->getOpcode()) {
    case ISD::BRCOND:
      return SelectBRCOND(Node);
    case PTXISD::READ_PARAM:
      return SelectREADPARAM(Node);
    case PTXISD::WRITE_PARAM:
      return SelectWRITEPARAM(Node);
    case ISD::FrameIndex:
      return SelectFrameIndex(Node);
    default:
      return SelectCode(Node);
  }
}

SDNode *PTXDAGToDAGISel::SelectBRCOND(SDNode *Node) {
  assert(Node->getNumOperands() >= 3);

  SDValue Chain  = Node->getOperand(0);
  SDValue Pred   = Node->getOperand(1);
  SDValue Target = Node->getOperand(2); // branch target
  SDValue PredOp = CurDAG->getTargetConstant(PTXPredicate::Normal, MVT::i32);
  DebugLoc dl = Node->getDebugLoc();

  assert(Target.getOpcode()  == ISD::BasicBlock);
  assert(Pred.getValueType() == MVT::i1);

  // Emit BRAdp
  SDValue Ops[] = { Target, Pred, PredOp, Chain };
  return CurDAG->getMachineNode(PTX::BRAdp, dl, MVT::Other, Ops, 4);
}

SDNode *PTXDAGToDAGISel::SelectREADPARAM(SDNode *Node) {
  SDValue Chain = Node->getOperand(0);
  SDValue Index = Node->getOperand(1);

  int OpCode;

  // Get the type of parameter we are reading
  EVT VT = Node->getValueType(0);
  assert(VT.isSimple() && "READ_PARAM only implemented for MVT types");

  MVT Type = VT.getSimpleVT();

  if (Type == MVT::i1)
    OpCode = PTX::READPARAMPRED;
  else if (Type == MVT::i16)
    OpCode = PTX::READPARAMI16;
  else if (Type == MVT::i32)
    OpCode = PTX::READPARAMI32;
  else if (Type == MVT::i64)
    OpCode = PTX::READPARAMI64;
  else if (Type == MVT::f32)
    OpCode = PTX::READPARAMF32;
  else {
    assert(Type == MVT::f64 && "Unexpected type!");
    OpCode = PTX::READPARAMF64;
  }

  SDValue Pred = CurDAG->getRegister(PTX::NoRegister, MVT::i1);
  SDValue PredOp = CurDAG->getTargetConstant(PTXPredicate::None, MVT::i32);
  DebugLoc dl = Node->getDebugLoc();

  SDValue Ops[] = { Index, Pred, PredOp, Chain };
  return CurDAG->getMachineNode(OpCode, dl, VT, Ops, 4);
}

SDNode *PTXDAGToDAGISel::SelectWRITEPARAM(SDNode *Node) {

  SDValue Chain = Node->getOperand(0);
  SDValue Value = Node->getOperand(1);

  int OpCode;

  //Node->dumpr(CurDAG);

  // Get the type of parameter we are writing
  EVT VT = Value->getValueType(0);
  assert(VT.isSimple() && "WRITE_PARAM only implemented for MVT types");

  MVT Type = VT.getSimpleVT();

  if (Type == MVT::i1)
    OpCode = PTX::WRITEPARAMPRED;
  else if (Type == MVT::i16)
    OpCode = PTX::WRITEPARAMI16;
  else if (Type == MVT::i32)
    OpCode = PTX::WRITEPARAMI32;
  else if (Type == MVT::i64)
    OpCode = PTX::WRITEPARAMI64;
  else if (Type == MVT::f32)
    OpCode = PTX::WRITEPARAMF32;
  else if (Type == MVT::f64)
    OpCode = PTX::WRITEPARAMF64;
  else
    llvm_unreachable("Invalid type in SelectWRITEPARAM");

  SDValue Pred = CurDAG->getRegister(PTX::NoRegister, MVT::i1);
  SDValue PredOp = CurDAG->getTargetConstant(PTXPredicate::None, MVT::i32);
  DebugLoc dl = Node->getDebugLoc();

  SDValue Ops[] = { Value, Pred, PredOp, Chain };
  SDNode* Ret = CurDAG->getMachineNode(OpCode, dl, MVT::Other, Ops, 4);

  //dbgs() << "SelectWRITEPARAM produced:\n\t";
  //Ret->dumpr(CurDAG);

  return Ret;
}

SDNode *PTXDAGToDAGISel::SelectFrameIndex(SDNode *Node) {
  int FI = cast<FrameIndexSDNode>(Node)->getIndex();
  //dbgs() << "Selecting FrameIndex at index " << FI << "\n";
  //SDValue TFI = CurDAG->getTargetFrameIndex(FI, Node->getValueType(0));

  PTXMachineFunctionInfo *MFI = MF->getInfo<PTXMachineFunctionInfo>();

  SDValue FrameSymbol = CurDAG->getTargetExternalSymbol(MFI->getFrameSymbol(FI),
                                                        Node->getValueType(0));

  return FrameSymbol.getNode();
}

// Match memory operand of the form [reg+reg]
bool PTXDAGToDAGISel::SelectADDRrr(SDValue &Addr, SDValue &R1, SDValue &R2) {
  if (Addr.getOpcode() != ISD::ADD || Addr.getNumOperands() < 2 ||
      isImm(Addr.getOperand(0)) || isImm(Addr.getOperand(1)))
    return false;

  assert(Addr.getValueType().isSimple() && "Type must be simple");

  R1 = Addr;
  R2 = CurDAG->getTargetConstant(0, Addr.getValueType().getSimpleVT());

  return true;
}

// Match memory operand of the form [reg], [imm+reg], and [reg+imm]
bool PTXDAGToDAGISel::SelectADDRri(SDValue &Addr, SDValue &Base,
                                   SDValue &Offset) {
  // FrameIndex addresses are handled separately
  //errs() << "SelectADDRri: ";
  //Addr.getNode()->dumpr();
  if (isa<FrameIndexSDNode>(Addr)) {
    //errs() << "Failure\n";
    return false;
  }

  if (CurDAG->isBaseWithConstantOffset(Addr)) {
    Base = Addr.getOperand(0);
    if (isa<FrameIndexSDNode>(Base)) {
      //errs() << "Failure\n";
      return false;
    }
    ConstantSDNode *CN = dyn_cast<ConstantSDNode>(Addr.getOperand(1));
    Offset = CurDAG->getTargetConstant(CN->getZExtValue(), MVT::i32);
    //errs() << "Success\n";
    return true;
  }

  /*if (Addr.getNumOperands() == 1) {
    Base = Addr;
    Offset = CurDAG->getTargetConstant(0, Addr.getValueType().getSimpleVT());
    errs() << "Success\n";
    return true;
  }*/

  //errs() << "SelectADDRri fails on: ";
  //Addr.getNode()->dumpr();

  if (isImm(Addr)) {
    //errs() << "Failure\n";
    return false;
  }

  Base = Addr;
  Offset = CurDAG->getTargetConstant(0, Addr.getValueType().getSimpleVT());

  //errs() << "Success\n";
  return true;

  /*if (Addr.getOpcode() != ISD::ADD) {
    // let SelectADDRii handle the [imm] case
    if (isImm(Addr))
      return false;
    // it is [reg]

    assert(Addr.getValueType().isSimple() && "Type must be simple");
    Base = Addr;
    Offset = CurDAG->getTargetConstant(0, Addr.getValueType().getSimpleVT());

    return true;
  }

  if (Addr.getNumOperands() < 2)
    return false;

  // let SelectADDRii handle the [imm+imm] case
  if (isImm(Addr.getOperand(0)) && isImm(Addr.getOperand(1)))
    return false;

  // try [reg+imm] and [imm+reg]
  for (int i = 0; i < 2; i ++)
    if (SelectImm(Addr.getOperand(1-i), Offset)) {
      Base = Addr.getOperand(i);
      return true;
    }

  // neither [reg+imm] nor [imm+reg]
  return false;*/
}

// Match memory operand of the form [imm+imm] and [imm]
bool PTXDAGToDAGISel::SelectADDRii(SDValue &Addr, SDValue &Base,
                                   SDValue &Offset) {
  // is [imm+imm]?
  if (Addr.getOpcode() == ISD::ADD) {
    return SelectImm(Addr.getOperand(0), Base) &&
           SelectImm(Addr.getOperand(1), Offset);
  }

  // is [imm]?
  if (SelectImm(Addr, Base)) {
    assert(Addr.getValueType().isSimple() && "Type must be simple");

    Offset = CurDAG->getTargetConstant(0, Addr.getValueType().getSimpleVT());

    return true;
  }

  return false;
}

// Match memory operand of the form [reg], [imm+reg], and [reg+imm]
bool PTXDAGToDAGISel::SelectADDRlocal(SDValue &Addr, SDValue &Base,
                                      SDValue &Offset) {
  //errs() << "SelectADDRlocal: ";
  //Addr.getNode()->dumpr();
  if (isa<FrameIndexSDNode>(Addr)) {
    Base = Addr;
    Offset = CurDAG->getTargetConstant(0, Addr.getValueType().getSimpleVT());
    //errs() << "Success\n";
    return true;
  }

  if (CurDAG->isBaseWithConstantOffset(Addr)) {
    Base = Addr.getOperand(0);
    if (!isa<FrameIndexSDNode>(Base)) {
      //errs() << "Failure\n";
      return false;
    }
    ConstantSDNode *CN = dyn_cast<ConstantSDNode>(Addr.getOperand(1));
    Offset = CurDAG->getTargetConstant(CN->getZExtValue(), MVT::i32);
    //errs() << "Offset: ";
    //Offset.getNode()->dumpr();
    //errs() << "Success\n";
    return true;
  }

  //errs() << "Failure\n";
  return false;
}

bool PTXDAGToDAGISel::isImm(const SDValue &operand) {
  return ConstantSDNode::classof(operand.getNode());
}

bool PTXDAGToDAGISel::SelectImm(const SDValue &operand, SDValue &imm) {
  SDNode *node = operand.getNode();
  if (!ConstantSDNode::classof(node))
    return false;

  ConstantSDNode *CN = cast<ConstantSDNode>(node);
  imm = CurDAG->getTargetConstant(*CN->getConstantIntValue(),
                                  operand.getValueType());
  return true;
}

const PTXSubtarget& PTXDAGToDAGISel::getSubtarget() const
{
  return TM.getSubtarget<PTXSubtarget>();
}