//===-- W65816ISelDAGToDAG.cpp - DAG to DAG inst selector for W65816 ------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Skeleton DAG-to-DAG selector for the W65816.  Falls back to the TableGen
// selector for every node; real custom selection (frame-index materialisation,
// addressing-mode matching, etc.) will be added once instructions exist.
//
//===----------------------------------------------------------------------===//

#include "W65816.h"
#include "W65816SelectionDAGInfo.h"
#include "W65816TargetMachine.h"
#include "llvm/CodeGen/SelectionDAG.h"
#include "llvm/CodeGen/SelectionDAGISel.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"

using namespace llvm;

#define DEBUG_TYPE "w65816-isel"
#define PASS_NAME "W65816 DAG->DAG Pattern Instruction Selection"

namespace {

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

  W65816DAGToDAGISel(W65816TargetMachine &TM, CodeGenOptLevel OptLevel)
      : SelectionDAGISel(TM, OptLevel) {}

private:
#include "W65816GenDAGISel.inc"

  void Select(SDNode *N) override;

  // ComplexPattern selector: matches a FrameIndex SDValue and returns
  // it (plus a zero offset) so LDAfi / STAfi can fold the FI directly
  // into their operand list.
  bool SelectFrameIndex(SDValue N, SDValue &Base, SDValue &Offset);
};

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

  W65816DAGToDAGISelLegacy(W65816TargetMachine &TM, CodeGenOptLevel OptLevel)
      : SelectionDAGISelLegacy(
            ID, std::make_unique<W65816DAGToDAGISel>(TM, OptLevel)) {}
};

} // end anonymous namespace

char W65816DAGToDAGISelLegacy::ID;

INITIALIZE_PASS(W65816DAGToDAGISelLegacy, DEBUG_TYPE, PASS_NAME, false, false)

FunctionPass *llvm::createW65816ISelDag(W65816TargetMachine &TM,
                                        CodeGenOptLevel OptLevel) {
  return new W65816DAGToDAGISelLegacy(TM, OptLevel);
}

void W65816DAGToDAGISel::Select(SDNode *Node) {
  if (Node->isMachineOpcode()) {
    Node->setNodeId(-1);
    return;
  }

  // Custom selection: bare FrameIndex SDValue used as a pointer value
  // (e.g. `&arr[0]` for a stack-allocated array).  The auto-generated
  // selector has no pattern for `(i16 frameindex)` because tablegen
  // doesn't expose FrameIndex as a leaf type — so ISel fails with
  // "Cannot select: FrameIndex" before ever reaching a load/store-
  // context fold.  Convert to ADDframe (FI, 0); the frame-index
  // elimination pass turns ADDframe into TSC + CLC + ADC #(offset +
  // stackSize), producing SP+offset in A.
  //
  // ptr32 mode: a `(i32 frameindex)` is `&local` typed as a 32-bit
  // pointer (bank+addr).  Lower as REG_SEQUENCE(ADDframe, sub_lo, 0,
  // sub_hi).  Hi=0 reflects the program-bank assumption (stack lives
  // in bank 0 for our crt0 startup).  Without this, ISel hits
  // "Cannot select: t# = FrameIndex<N>" and the pass crashes —
  // observed for softDouble's __adddf3 calling dclass(a, &sa, &ea,
  // &ma) where the latter three become i32 frameindex SDValues.
  if (Node->getOpcode() == ISD::FrameIndex) {
    SDLoc DL(Node);
    int FI = cast<FrameIndexSDNode>(Node)->getIndex();
    EVT VT = Node->getValueType(0);
    SDValue TFI = CurDAG->getTargetFrameIndex(FI, MVT::i16);
    SDValue Zero16 = CurDAG->getTargetConstant(0, DL, MVT::i16);
    if (VT == MVT::i16) {
      CurDAG->SelectNodeTo(Node, W65816::ADDframe, MVT::i16, TFI, Zero16);
      return;
    }
    if (VT == MVT::i32) {
      // Build (REG_SEQUENCE Wide32RC, ADDframe(FI,0), sub_lo, MOVi16(0),
      // sub_hi).  ADDframe materialises lo as an i16 SDValue; the hi
      // half is the literal bank byte (0).
      SDNode *Lo = CurDAG->getMachineNode(W65816::ADDframe, DL,
                                          MVT::i16, TFI, Zero16);
      SDValue HiC = CurDAG->getTargetConstant(0, DL, MVT::i16);
      // For the high half, just materialise an i16 zero via MOVi16imm.
      SDNode *Hi = CurDAG->getMachineNode(W65816::LDAi16imm, DL,
                                          MVT::i16, HiC);
      SDValue RC = CurDAG->getTargetConstant(W65816::Wide32RegClassID,
                                             DL, MVT::i32);
      SDValue SubLo = CurDAG->getTargetConstant(llvm::sub_lo, DL, MVT::i32);
      SDValue SubHi = CurDAG->getTargetConstant(llvm::sub_hi, DL, MVT::i32);
      CurDAG->SelectNodeTo(Node, TargetOpcode::REG_SEQUENCE, MVT::i32,
                           {RC, SDValue(Lo, 0), SubLo, SDValue(Hi, 0),
                            SubHi});
      return;
    }
    report_fatal_error("W65816: FrameIndex selection: unsupported VT");
  }

  // Defer to the auto-generated selector for everything else.
  SelectCode(Node);
}

bool W65816DAGToDAGISel::SelectFrameIndex(SDValue N, SDValue &Base,
                                          SDValue &Offset) {
  // Bare FrameIndex: offset 0.
  if (auto *FIN = dyn_cast<FrameIndexSDNode>(N)) {
    Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), MVT::i16);
    Offset = CurDAG->getTargetConstant(0, SDLoc(N), MVT::i16);
    return true;
  }
  // (add FrameIndex, const): fold the const into the memfi offset.
  // Type legalization emits this shape when splitting a multi-byte
  // load/store at a stack slot into multiple smaller loads (e.g. an
  // i32 spill becomes two i16 loads, with the high load at FI+2).
  // Without this, the bare FrameIndex inside the add is left as an
  // unmatched i16 leaf and ISel reports "Cannot select FrameIndex".
  if (N.getOpcode() == ISD::ADD) {
    SDValue LHS = N.getOperand(0);
    SDValue RHS = N.getOperand(1);
    if (auto *FIN = dyn_cast<FrameIndexSDNode>(LHS)) {
      if (auto *CN = dyn_cast<ConstantSDNode>(RHS)) {
        Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), MVT::i16);
        Offset = CurDAG->getTargetConstant(CN->getSExtValue(),
                                           SDLoc(N), MVT::i16);
        return true;
      }
    }
  }
  return false;
}
