//===-- W65816WidenAcc16.cpp - Promote Acc16 vregs to Wide16 ------------===//
//
// 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-RA pass that promotes Acc16 vregs (constrained to physreg A only)
// to the wider Wide16 class (A + IMG0..IMG7).  Greedy regalloc gets
// 9-way pressure relief on the i16 register class; functions that
// previously failed with "ran out of registers" can now spread their
// live i16 values across A and the DP-backed imaginaries.
//
// Cross-class moves between A and IMGn are LDA/STA dp (4 cyc each way,
// 2 bytes), emitted by W65816InstrInfo::copyPhysReg.  The constraint
// that arithmetic ops require their source in A propagates back from
// the use sites — regalloc coerces Wide16 vregs to Acc16 (= {A}) at
// those sites and inserts the necessary COPYs.
//
// Calls clobber IMGn (caller-save), so any vreg in IMGn that lives
// across a call gets spilled to stack by regalloc.  This pass doesn't
// model that explicitly — it relies on the calling convention's
// regmask to mark IMGn clobbered.
//
//===----------------------------------------------------------------------===//

#include "W65816.h"
#include "W65816InstrInfo.h"
#include "W65816Subtarget.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"

using namespace llvm;

#define DEBUG_TYPE "w65816-widen-acc16"

namespace {

class W65816WidenAcc16 : public MachineFunctionPass {
public:
  static char ID;
  W65816WidenAcc16() : MachineFunctionPass(ID) {}
  StringRef getPassName() const override {
    return "W65816 Acc16 → Wide16 promotion";
  }
  void getAnalysisUsage(AnalysisUsage &AU) const override {
    AU.setPreservesCFG();
    MachineFunctionPass::getAnalysisUsage(AU);
  }
  bool runOnMachineFunction(MachineFunction &MF) override;
};

} // namespace

char W65816WidenAcc16::ID = 0;

INITIALIZE_PASS(W65816WidenAcc16, DEBUG_TYPE,
                "W65816 Acc16 → Wide16 promotion", false, false)

FunctionPass *llvm::createW65816WidenAcc16() {
  return new W65816WidenAcc16();
}

// Returns true if the vreg has any physreg-COPY use that would conflict
// with Wide16 class assignment.  $a is a member of Wide16 (Wide16 = A +
// IMG0..15), so a COPY to $a is fine — the vreg can be Wide16 and
// regalloc will pick $a to coalesce.  $x / $y are in Idx16, NOT in
// Wide16, so a COPY to those forces the vreg to NOT be in Wide16
// (verifier would reject).
static bool flowsToIncompatiblePhysReg(Register VReg,
                                       const MachineRegisterInfo &MRI) {
  for (auto &U : MRI.use_nodbg_instructions(VReg)) {
    if (!U.isCopy()) continue;
    const MachineOperand &Dst = U.getOperand(0);
    if (!Dst.isReg() || !Dst.getReg().isPhysical()) continue;
    Register P = Dst.getReg();
    if (P == W65816::A) continue;
    if (P >= W65816::IMG0 && P <= W65816::IMG15) continue;
    return true;
  }
  return false;
}

// Returns true if the vreg is used by any PHI.  PHI input/result must
// share the same register class (verifier requirement).  Rather than
// also widen the PHI's result and recursively all of its uses, we skip
// vregs caught up in PHIs entirely — leaves a few wins on the table
// but avoids cross-MBB analysis here.
static bool usedByPhi(Register VReg, const MachineRegisterInfo &MRI) {
  for (auto &U : MRI.use_nodbg_instructions(VReg)) {
    if (U.isPHI()) return true;
  }
  return false;
}

// Returns true if all non-debug, non-COPY uses of VReg are at operands
// whose required register class accepts Wide16 (i.e., Wide16 or a
// superclass).  COPY uses are unconstrained — fine.  PHI uses already
// filtered earlier.  If any use's operand class is strictly narrower
// than Wide16 (i.e., Acc16-only, Idx16-only, etc.), return false: the
// verifier rejects passing a Wide16 vreg to such an operand.
static bool allUsesAcceptWide(Register VReg,
                              const MachineRegisterInfo &MRI,
                              const TargetRegisterInfo &TRI,
                              const TargetInstrInfo &TII) {
  for (auto &MO : MRI.use_nodbg_operands(VReg)) {
    MachineInstr *UMI = MO.getParent();
    if (UMI->isCopy()) continue;  // COPY accepts anything
    if (UMI->isPHI()) return false;  // already filtered, but be safe
    unsigned OpIdx = UMI->getOperandNo(&MO);
    (void)TRI;
    const TargetRegisterClass *Expected =
        TII.getRegClass(UMI->getDesc(), OpIdx);
    if (!Expected) continue;  // no constraint
    if (Expected == &W65816::Wide16RegClass) continue;
    // Check superclass relationship: Wide16 must be a sub-or-equal of
    // Expected for the use to accept Wide16 vregs.  A common case:
    // Expected is a superclass that includes Wide16.  If Expected is
    // narrower (e.g., Acc16 only), reject.
    if (Expected->hasSubClassEq(&W65816::Wide16RegClass)) continue;
    return false;
  }
  return true;
}

bool W65816WidenAcc16::runOnMachineFunction(MachineFunction &MF) {
  if (!MF.getRegInfo().getNumVirtRegs()) return false;
  // At -O0 / optnone, register coalescing doesn't run, so the COPY we
  // insert to bridge Acc16 → Wide16 doesn't get folded; instead it
  // forces wide16 spills through DP-mapped slots that collide and
  // produce miscompiles around modify-in-place ops (lda dp; inc a;
  // sta dp; lda dp reads pre-inc value).  The promotion is purely a
  // performance optimization, so skip it for optnone functions.
  if (MF.getFunction().hasOptNone()) return false;
  MachineRegisterInfo &MRI = MF.getRegInfo();
  const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
  const W65816InstrInfo *TII = STI.getInstrInfo();
  const TargetRegisterInfo *TRI = STI.getRegisterInfo();
  bool Changed = false;

  // For each Acc16 vreg, insert a COPY to a fresh Wide16 vreg right
  // after its def, then redirect all uses to the Wide16 vreg.  The
  // original Acc16 vreg keeps its tight constraint (= {A}) for the
  // def site (which is typically a pseudo whose AsmPrinter expansion
  // assumes A); the new Wide16 vreg is free for greedy to allocate
  // anywhere in {A, IMG0..IMG7}.  When both end up in A, the COPY
  // is a no-op the regalloc/coalescer collapses; when the Wide16
  // vreg lands on IMGn, the COPY becomes STA dp via copyPhysReg.
  SmallVector<Register, 16> Candidates;
  for (unsigned i = 0; i < MRI.getNumVirtRegs(); ++i) {
    Register VReg = Register::index2VirtReg(i);
    if (MRI.def_empty(VReg)) continue;
    if (MRI.getRegClass(VReg) != &W65816::Acc16RegClass) continue;
    if (flowsToIncompatiblePhysReg(VReg, MRI)) continue;
    if (usedByPhi(VReg, MRI)) continue;
    if (!MRI.hasOneDef(VReg)) continue;  // require single SSA def
    if (!allUsesAcceptWide(VReg, MRI, *TRI, *TII)) continue;
    Candidates.push_back(VReg);
  }

  for (Register VReg : Candidates) {
    MachineInstr *DefMI = &*MRI.def_instructions(VReg).begin();
    MachineBasicBlock *MBB = DefMI->getParent();
    DebugLoc DL = DefMI->getDebugLoc();
    Register WideReg = MRI.createVirtualRegister(&W65816::Wide16RegClass);
    // Insert AFTER the def, but if the def is a PHI, walk past all
    // PHIs in the block first — verifier requires all PHIs at MBB
    // entry, no non-PHI may sit between them.
    auto InsertAt = std::next(MachineBasicBlock::iterator(DefMI));
    if (DefMI->isPHI()) {
      while (InsertAt != MBB->end() && InsertAt->isPHI()) ++InsertAt;
    }
    BuildMI(*MBB, InsertAt, DL, TII->get(TargetOpcode::COPY), WideReg)
        .addReg(VReg);
    // Rewrite all non-debug uses of VReg (other than the COPY we just
    // inserted) to WideReg.
    SmallVector<MachineOperand *, 8> ToRewrite;
    for (auto &U : MRI.use_nodbg_operands(VReg)) {
      MachineInstr *UMI = U.getParent();
      if (UMI->getOpcode() == TargetOpcode::COPY &&
          UMI->getOperand(0).getReg() == WideReg) continue;
      ToRewrite.push_back(&U);
    }
    for (auto *MO : ToRewrite) {
      MO->setReg(WideReg);
      MO->setIsKill(false);
    }
    Changed = true;
  }

  // Phase 2: PHI cycle widening was prototyped here but never landed.
  // The prototype body lived in an #if 0 block that was removed once
  // we settled on Phase 1 as the only effective half of the pass.
  // Why disabled (2026-05-13 attempt):
  // - Widening PHI cycles to Wide16 (= A + IMG0..15) is undone by the
  //   Register Coalescer: it joins our Wide16 vregs with adjacent
  //   Acc16 vregs via the bridge COPYs we insert, and the resulting
  //   joint class is `intersect(Wide16, Acc16) = Acc16`.  Net effect:
  //   no IMG, just more code through the coalescer.
  // - Switching to Img16 (= IMG0..15, no A) defeats the coalescer
  //   (intersection with Acc16 is ∅) but forces ALL widened PHIs into
  //   IMG slots even when A would be better, AND triggers cascading
  //   copyPhysReg paths that aren't all implemented (e.g., DPF0 → IMG
  //   for i64 libcall return values), aborting clang on runtime builds.
  // - A targeted fix needs either (a) a class that the coalescer
  //   refuses to join with Acc16 yet that still allows A as a member,
  //   (b) a post-coalescer pass that re-widens specific high-traffic
  //   vregs back to Img16, or (c) regalloc cost-model tuning so it
  //   prefers IMG8..15 over stack for loop-live values.
  return Changed;
}
