//===-- W65816LowerWide32.cpp - Wide32 -> 2x i16 pre-RA lowering ---------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Pre-regalloc pass that decomposes Wide32 register-pair vregs into pairs
// of i16 vregs.  Without this, greedy / basic regalloc fails on i64-heavy
// functions (`RegAllocBase` crashes during `allocatePhysRegs`) because
// the i64-via-2-i32-via-Wide32 chain produces too many simultaneously
// live register-pair vregs.  After this pass, only i16 vregs remain at
// the regalloc input — Wide32 lives only inside this pass and the new
// LDAptr32S / STAptr32S / STBptr32S pseudos that take 2 i16 ptr operands
// directly.
//
// Walks the MIR and:
//   1. Finds REG_SEQUENCE producing Wide32 / Acc32 / AnyWide32; records
//      the (lo, hi) i16 source operands; queues the REG_SEQUENCE for
//      erasure.
//   2. Finds COPY whose dest is a Wide32 vreg and whose src is another
//      mapped Wide32 vreg; chains the (lo, hi) mapping forward.
//   3. Rewrites EXTRACT_SUBREG of mapped Wide32 vregs by replacing the
//      destination vreg with the appropriate half (sub_lo or sub_hi).
//   4. Rewrites LDAptr32 / STAptr32 / STBptr32 with a mapped Wide32 ptr
//      to the corresponding LDAptr32S / STAptr32S / STBptr32S pseudo
//      with two separate i16 operands.
//
// Bail / safety: any Wide32 vreg whose def we can't decompose is left
// in place — regalloc may still struggle but no miscompile.
//
//===----------------------------------------------------------------------===//

#include "W65816.h"
#include "W65816InstrInfo.h"
#include "W65816Subtarget.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

#define DEBUG_TYPE "w65816-lower-wide32"

namespace {

class W65816LowerWide32 : public MachineFunctionPass {
public:
  static char ID;
  W65816LowerWide32() : MachineFunctionPass(ID) {}
  StringRef getPassName() const override {
    return "W65816 Wide32 -> 2x i16 lowering";
  }
  void getAnalysisUsage(AnalysisUsage &AU) const override {
    AU.setPreservesCFG();
    MachineFunctionPass::getAnalysisUsage(AU);
  }
  bool runOnMachineFunction(MachineFunction &MF) override;
};

} // namespace

char W65816LowerWide32::ID = 0;

INITIALIZE_PASS(W65816LowerWide32, DEBUG_TYPE,
                "W65816 Wide32 lowering", false, false)

FunctionPass *llvm::createW65816LowerWide32() {
  return new W65816LowerWide32();
}

static bool isWide32RC(const TargetRegisterClass *RC) {
  return RC == &W65816::Wide32RegClass ||
         RC == &W65816::Acc32RegClass ||
         RC == &W65816::AnyWide32RegClass;
}

bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
  MachineRegisterInfo &MRI = MF.getRegInfo();
  const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
  const W65816InstrInfo &TII = *STI.getInstrInfo();

  // Map: Wide32 vreg -> (loVreg, hiVreg) of i16 type.
  DenseMap<Register, std::pair<Register, Register>> wideMap;

  // Pass 1: collect all Wide32 vregs.
  SmallVector<Register, 16> wide32Vregs;
  for (unsigned i = 0, e = MRI.getNumVirtRegs(); i != e; ++i) {
    Register R = Register::index2VirtReg(i);
    if (MRI.reg_nodbg_empty(R))
      continue;
    if (!isWide32RC(MRI.getRegClass(R)))
      continue;
    wide32Vregs.push_back(R);
  }

  if (wide32Vregs.empty())
    return false;

  // Pass 2: process REG_SEQUENCE / chained-COPY / multi-subreg-def
  // shapes; build the mapping.  Iterate to fixed point because COPY
  // chains depend on prior mappings.
  SmallVector<MachineInstr *, 16> toErase;
  bool changed = true;
  while (changed) {
    changed = false;
    for (Register W : wide32Vregs) {
      if (wideMap.count(W))
        continue;
      MachineInstr *DefMI = MRI.getUniqueVRegDef(W);
      if (DefMI && DefMI->getOpcode() == TargetOpcode::REG_SEQUENCE) {
        Register Lo, Hi;
        bool Bail = false;
        for (unsigned op = 1; op + 1 < DefMI->getNumOperands(); op += 2) {
          if (!DefMI->getOperand(op).isReg() ||
              !DefMI->getOperand(op + 1).isImm())
            continue;
          unsigned idx = DefMI->getOperand(op + 1).getImm();
          Register Src = DefMI->getOperand(op).getReg();
          unsigned SrcSub = DefMI->getOperand(op).getSubReg();
          // If the source has a sub-register specifier (e.g.
          // `%W.sub_lo:wide32` is a slice of a wide32 vreg), the
          // effective "half" is the corresponding half of that source.
          // Resolve via wideMap when the parent is already mapped;
          // otherwise defer until a later iteration picks it up.
          if (SrcSub != 0) {
            if (!Src.isVirtual() || !wideMap.count(Src)) {
              Bail = true;
              break;
            }
            auto [SrcLo, SrcHi] = wideMap[Src];
            Src = (SrcSub == llvm::sub_lo) ? SrcLo : SrcHi;
          }
          if (idx == llvm::sub_lo)
            Lo = Src;
          else if (idx == llvm::sub_hi)
            Hi = Src;
        }
        if (Bail) continue;
        if (Lo && Hi) {
          wideMap[W] = {Lo, Hi};
          toErase.push_back(DefMI);
          changed = true;
          continue;
        }
      }
      if (DefMI && DefMI->isCopy()) {
        Register Src = DefMI->getOperand(1).getReg();
        if (Src.isVirtual() && wideMap.count(Src)) {
          // Copy the source mapping into a local before writing wideMap[W]:
          // DenseMap may rehash when inserting W, invalidating any reference
          // returned by operator[](Src).  Naive `wideMap[W] = wideMap[Src]`
          // dangles after rehash and writes zeros into the new entry.
          auto SrcPair = wideMap[Src];
          wideMap[W] = SrcPair;
          toErase.push_back(DefMI);
          changed = true;
          continue;
        }
      }
      // Multi-subreg-def shape: separate sub-reg COPYs build %W:
      //   undef %W.sub_lo:wide32 = COPY %A:acc16
      //   %W.sub_hi:wide32 = COPY %B:acc16
      // Equivalent to a REG_SEQUENCE %A, sub_lo, %B, sub_hi.  softDouble
      // at -O2 generates this heavily; without handling it the Wide32
      // vreg survives to regalloc, which then asks for a spill/reload
      // from a non-pair physreg and trips load/storeRegToStackSlot's
      // llvm_unreachable.
      Register LoSrc, HiSrc;
      MachineInstr *LoDefMI = nullptr;
      MachineInstr *HiDefMI = nullptr;
      bool ok = true;
      bool Bail = false;
      for (MachineInstr &MI : MRI.def_instructions(W)) {
        if (!MI.isCopy()) { ok = false; break; }
        const MachineOperand &Dst = MI.getOperand(0);
        const MachineOperand &Src = MI.getOperand(1);
        if (!Dst.isReg() || Dst.getReg() != W) { ok = false; break; }
        unsigned SubIdx = Dst.getSubReg();
        Register S = Src.getReg();
        unsigned SrcSub = Src.getSubReg();
        // If the source has a sub-register specifier, resolve through
        // wideMap[parent].  Symmetric with the REG_SEQUENCE handler
        // above — without this, `%W.sub_lo = COPY %V.sub_lo:wide32`
        // records the wide32 parent %V instead of %V's i16 sub_lo.
        if (SrcSub != 0) {
          if (!S.isVirtual() || !wideMap.count(S)) { Bail = true; break; }
          auto [SL, SH] = wideMap[S];
          S = (SrcSub == llvm::sub_lo) ? SL : SH;
        }
        if (SubIdx == llvm::sub_lo) {
          if (LoDefMI) { ok = false; break; }
          LoDefMI = &MI;
          LoSrc = S;
        } else if (SubIdx == llvm::sub_hi) {
          if (HiDefMI) { ok = false; break; }
          HiDefMI = &MI;
          HiSrc = S;
        } else {
          ok = false;
          break;
        }
      }
      if (Bail) continue;
      if (ok && LoSrc && HiSrc) {
        wideMap[W] = {LoSrc, HiSrc};
        if (LoDefMI) toErase.push_back(LoDefMI);
        if (HiDefMI) toErase.push_back(HiDefMI);
        changed = true;
      }
    }
  }

  // Pass 2b: handle PHIs whose result is a Wide32 vreg by splitting
  // into 2 PHIs (one per half).  Iterate to fixed point: a PHI becomes
  // resolvable only after all its sources have been mapped.
  changed = true;
  while (changed) {
    changed = false;
    for (Register W : wide32Vregs) {
      if (wideMap.count(W))
        continue;
      MachineInstr *DefMI = MRI.getUniqueVRegDef(W);
      if (!DefMI || !DefMI->isPHI())
        continue;
      bool AllMapped = true;
      for (unsigned op = 1; op + 1 < DefMI->getNumOperands(); op += 2) {
        Register Src = DefMI->getOperand(op).getReg();
        if (!Src.isVirtual() || !wideMap.count(Src)) {
          AllMapped = false;
          break;
        }
      }
      if (!AllMapped)
        continue;
      Register NewLo = MRI.createVirtualRegister(&W65816::Acc16RegClass);
      Register NewHi = MRI.createVirtualRegister(&W65816::Acc16RegClass);
      MachineBasicBlock *MBB = DefMI->getParent();
      DebugLoc DL = DefMI->getDebugLoc();
      auto PHILo = BuildMI(*MBB, DefMI, DL, TII.get(TargetOpcode::PHI), NewLo);
      auto PHIHi = BuildMI(*MBB, DefMI, DL, TII.get(TargetOpcode::PHI), NewHi);
      for (unsigned op = 1; op + 1 < DefMI->getNumOperands(); op += 2) {
        Register Src = DefMI->getOperand(op).getReg();
        MachineBasicBlock *PredMBB = DefMI->getOperand(op + 1).getMBB();
        auto [SrcLo, SrcHi] = wideMap[Src];
        PHILo.addReg(SrcLo).addMBB(PredMBB);
        PHIHi.addReg(SrcHi).addMBB(PredMBB);
      }
      wideMap[W] = {NewLo, NewHi};
      toErase.push_back(DefMI);
      changed = true;
    }
  }

  // Pass 3: rewrite uses.
  SmallVector<MachineInstr *, 32> useToErase;
  for (MachineBasicBlock &MBB : MF) {
    for (MachineBasicBlock::iterator It = MBB.begin(); It != MBB.end();) {
      MachineInstr *MI = &*It++;

      // EXTRACT_SUBREG of a mapped Wide32 vreg: replace the dest vreg
      // with the appropriate half (sub_lo or sub_hi).
      if (MI->getOpcode() == TargetOpcode::EXTRACT_SUBREG) {
        Register Src = MI->getOperand(1).getReg();
        if (Src.isVirtual() && wideMap.count(Src)) {
          unsigned SubIdx = MI->getOperand(2).getImm();
          auto [Lo, Hi] = wideMap[Src];
          Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi;
          Register Dst = MI->getOperand(0).getReg();
          MRI.replaceRegWith(Dst, Half);
          useToErase.push_back(MI);
          continue;
        }
      }

      // COPY %V.sub_lo / %V.sub_hi (partial-reg COPY where source has a
      // sub-reg specifier and the source vreg is a mapped Wide32).
      // LLVM emits this shape instead of EXTRACT_SUBREG when projecting
      // a half out of a Wide32 vreg.  Only the shape with a full-reg
      // destination is handled here — partial-reg destinations would
      // imply the dst is itself a Wide32 sub-reg def, which the def-side
      // multi-subreg-def handling covers separately.
      if (MI->isCopy()) {
        const MachineOperand &SrcOp = MI->getOperand(1);
        const MachineOperand &DstOp = MI->getOperand(0);
        if (SrcOp.isReg() && SrcOp.getReg().isVirtual() &&
            wideMap.count(SrcOp.getReg()) && SrcOp.getSubReg() != 0 &&
            DstOp.isReg() && DstOp.getSubReg() == 0) {
          unsigned SubIdx = SrcOp.getSubReg();
          auto [Lo, Hi] = wideMap[SrcOp.getReg()];
          Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi;
          MRI.replaceRegWith(DstOp.getReg(), Half);
          useToErase.push_back(MI);
          continue;
        }
      }

      // LDAptr32 / STAptr32 / STBptr32 with a mapped Wide32 ptr:
      // rewrite to LDAptr32S / STAptr32S / STBptr32S.
      unsigned Opc = MI->getOpcode();
      bool isPtrOp = (Opc == W65816::LDAptr32 || Opc == W65816::STAptr32 ||
                      Opc == W65816::STBptr32);
      if (isPtrOp) {
        Register Ptr = MI->getOperand(1).getReg();
        if (Ptr.isVirtual() && wideMap.count(Ptr)) {
          auto [Lo, Hi] = wideMap[Ptr];
          unsigned NewOpc = (Opc == W65816::LDAptr32) ? W65816::LDAptr32S
                          : (Opc == W65816::STAptr32) ? W65816::STAptr32S
                                                      : W65816::STBptr32S;
          DebugLoc DL = MI->getDebugLoc();
          MachineBasicBlock *ParentMBB = MI->getParent();
          if (Opc == W65816::LDAptr32) {
            Register Dst = MI->getOperand(0).getReg();
            BuildMI(*ParentMBB, MI->getIterator(), DL, TII.get(NewOpc), Dst)
                .addReg(Lo)
                .addReg(Hi);
          } else {
            Register Val = MI->getOperand(0).getReg();
            BuildMI(*ParentMBB, MI->getIterator(), DL, TII.get(NewOpc))
                .addReg(Val)
                .addReg(Lo)
                .addReg(Hi);
          }
          useToErase.push_back(MI);
          continue;
        }
      }
    }
  }

  // Erase use-side instructions (EXTRACT_SUBREG, LDAptr32-family) first
  // so the Wide32 vreg becomes dead.
  //
  // DBG_VALUE preservation: MRI.replaceRegWith above already redirected
  // any debug operands to the new half-vreg (replaceRegWith walks
  // reg_operands which includes debug ops).  No further work needed for
  // the EXTRACT_SUBREG / partial-COPY paths since the user-visible value
  // is preserved in the half vreg.  For LDAptr32 rewrites we don't move
  // a user-named value so DBG_VALUE attribution is unaffected.
  for (auto *MI : useToErase)
    MI->eraseFromParent();

  // Now check each REG_SEQUENCE / chained-COPY def: only erase if the
  // Wide32 vreg has no remaining uses.  Any leftover use means the pass
  // didn't cover that opcode — leaving the def in place keeps the MIR
  // well-formed (at the cost of pair-allocation pressure for that
  // specific case).
  //
  // Iterate to fixed point: a chained-COPY pattern like
  //   %114:wide32 = REG_SEQUENCE ...
  //   %74:wide32 = COPY %114
  //   ... uses of %74 ...
  // queues both the REG_SEQUENCE and the COPY for erasure.  Pass 3
  // rewrites %74's uses, leaving %74 dead.  In a single-pass erase,
  // %114 still has its COPY use at the time we check, so the REG_-
  // SEQUENCE is skipped — but then we erase the COPY, leaving %114
  // dead too.  Loop until no more erasures.
  bool eraseAny = !useToErase.empty();
  bool progress = true;
  while (progress) {
    progress = false;
    for (auto *&MI : toErase) {
      if (!MI)
        continue;
      if (MI->getNumOperands() == 0) {
        MI = nullptr;
        continue;
      }
      Register Dst = MI->getOperand(0).getReg();
      if (!Dst.isVirtual() || MRI.use_nodbg_empty(Dst)) {
        // DBG_VALUE preservation: use_nodbg_empty ignores debug uses, so
        // erasing here can strand DBG_VALUEs referring to Dst.  The
        // wide32 vreg has no surviving half mapping that's directly
        // usable (a wide32-typed variable is implemented as two i16
        // halves but DBG_VALUE expects a single location), so mark
        // dependent DBG_VALUEs as undef rather than emit a wrong
        // location.  A future pass could split the variable across
        // DW_OP_piece(lo)+DW_OP_piece(hi); not in scope here.
        if (Dst.isVirtual()) {
          // Dedup: DBG_VALUE_LIST may appear multiple times in the
          // use-list when several of its operands reference Dst.
          SmallPtrSet<MachineInstr *, 4> DbgUserSet;
          SmallVector<MachineInstr *, 4> DbgUsers;
          for (MachineInstr &U : MRI.use_instructions(Dst)) {
            if (U.isDebugValue() && DbgUserSet.insert(&U).second)
              DbgUsers.push_back(&U);
          }
          for (MachineInstr *DbgMI : DbgUsers) {
            for (MachineOperand &Op : DbgMI->debug_operands()) {
              if (Op.isReg() && Op.getReg() == Dst) {
                Op.setReg(0);
                Op.setSubReg(0);
              }
            }
          }
        }
        MI->eraseFromParent();
        MI = nullptr;
        eraseAny = true;
        progress = true;
      }
    }
  }

  return eraseAny;
}
