//===-- W65816FrameLowering.cpp - W65816 Frame Information ----------------===//
//
// 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 frame lowering.  The 65816 stack grows downward and has a 1-byte
// alignment; prologue and epilogue emission will be implemented alongside
// the full calling convention.
//
//===----------------------------------------------------------------------===//

#include "W65816FrameLowering.h"
#include "W65816InstrInfo.h"
#include "W65816MachineFunctionInfo.h"
#include "W65816Subtarget.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/ErrorHandling.h"

using namespace llvm;

// (The pure-i8-detection helpers were removed when the prologue went
// to "always 16-bit M".  See emitPrologue comment.)
//
// (DBR-zero wrap was prototyped here — PHB at function entry to save
// caller's DBR, set DBR=0, restore at exit.  Two issues blocked it:
// (a) saving DBR to a DP slot ($F2/$F3) conflicts with libgcc's
// muldi3/divdi3 scratch — those routines use $F2..$F8 freely, so
// the saved DBR doesn't survive a libcall in the function body.
// (b) saving via PHB shifts SP, which means LowerFormalArguments
// would need to bump every arg's StackOffset by 1 — but at
// LowerFormalArguments time we don't know yet whether the function
// will need the wrap (indirect-Y emission is a later lowering
// choice).  Right approach is a per-function attribute the user
// opts into, plus PEI integration to add a fixed-size "saved DBR"
// slot.  Deferred — see STATUS.md.)

W65816FrameLowering::W65816FrameLowering(const W65816Subtarget &STI)
    : TargetFrameLowering(TargetFrameLowering::StackGrowsDown, Align(1), 0,
                          Align(1)) {}

bool W65816FrameLowering::hasFPImpl(const MachineFunction &MF) const {
  // The 65816 has no native frame pointer — direct page would be the
  // logical home for one but we don't reserve it.  Return false so
  // every function uses pure stack-relative addressing for locals.
  // Variable-size objects (alloca with non-constant size) and
  // frameaddress() intrinsics will need a real FP eventually.
  return false;
}

bool W65816FrameLowering::hasReservedCallFrame(const MachineFunction &MF) const {
  // Returning false is required for correctness: LowerCall pushes
  // outgoing args via PUSH16 (PHA), which incrementally shifts SP
  // between ADJCALLSTACKDOWN and ADJCALLSTACKUP.  With a reserved
  // call frame, PEI assumes SP is stable across calls and bakes
  // FrameOffset+StackSize into LDA_StackRel.  Then any FI access
  // that the scheduler interleaves with pushed args (e.g. loading
  // a *later* arg from the caller's frame to push it) reads from
  // the wrong offset — silently miscompiling 2+ arg libcalls.
  // hasReservedCallFrame=false makes PEI add the DOWN-amount to
  // FI offsets between ADJCALLSTACKDOWN and ADJCALLSTACKUP,
  // recovering correctness.
  return false;
}

void W65816FrameLowering::emitPrologue(MachineFunction &MF,
                                       MachineBasicBlock &MBB) const {
  // Pick canonical mode based on whether the function uses any 8-bit
  // accumulator values.  Pure-i16 functions get REP #$30 (16-bit M+X);
  // any-i8 functions get REP #$10 + SEP #$20 (16-bit X, 8-bit A).
  // This is a coarse heuristic until the REP/SEP scheduling pass
  // (design doc §3.3) can insert mode transitions per-region.
  const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
  const W65816InstrInfo &TII = *STI.getInstrInfo();
  const MachineRegisterInfo &MRI = MF.getRegInfo();
  MachineBasicBlock::iterator MBBI = MBB.begin();
  DebugLoc DL;

  // Always enter in 16-bit M+X (REP #$30).  Per-instruction i8 ops wrap
  // themselves with SEP #$20 / REP #$20 in their AsmPrinter expansion;
  // W65816SepRepCleanup coalesces adjacent toggles so back-to-back i8
  // ops collapse into a single SEP/REP region (recovering the byte-
  // heavy "pure-i8" prologue's efficiency without its hazards).
  //
  // The earlier "pure-i8" heuristic (REP #$10 + SEP #$20 prologue) was
  // a silent miscompile: late-stage i8→i16 sign extension and any other
  // i16 op the back-end emits *without* a wrap — `and #$ff`, `eor #$80`,
  // `adc #$ff80`, etc. — would assemble as 3-byte i16 immediates but
  // execute in M=1 where the CPU only reads the low byte.  The next
  // immediate byte then becomes the next opcode (often $00 = BRK).
  // Caught by tracing inc_g for `char inc_g(void) { g++; return g; }`.
  (void)MRI;
  MF.getInfo<W65816MachineFunctionInfo>()->setUsesAcc8(false);
  BuildMI(MBB, MBBI, DL, TII.get(W65816::REP)).addImm(0x30);

  // Reserve stack space for locals/spills.
  //
  // Critical: arg0 lives in A on entry, so the prologue MUST NOT
  // clobber A.  The naive `TSC; SEC; SBC #N; TCS` sequence destroys A
  // (TSC overwrites A with SP) — used to silently corrupt arg0 in
  // every function with a stack frame, until this fix.
  //
  // Strategy (16-bit M):
  //   - Small frames (N <= 14 bytes): use N/2 `PHA` instructions.  PHA
  //     pushes A's value (whatever it is — including arg0) and only
  //     decrements S.  A is not modified.  N/2 bytes of code per call.
  //     Side-effect: the bytes pushed contain copies of arg0; the body's
  //     regalloc-inserted spills may overwrite them, which is fine.
  //   - Larger frames: TAY/TSC/.../TYA — 8 bytes total, preserves A
  //     through Y as a temporary.  Y is caller-saved by our (loose) ABI.
  //
  // Strategy (8-bit M): PHA in 8-bit M pushes 1 byte, so N PHAs for
  // N bytes.  Without this, spills land on top of the return address
  // and corrupt it (was a latent silent crash for 8-bit M functions
  // that needed any spilling).
  uint64_t StackSize = MF.getFrameInfo().getStackSize();
  bool HasVLA = MF.getFrameInfo().hasVarSizedObjects();

  // For VLA functions, save entry SP to DP $F4..$F5 BEFORE any frame
  // allocation so the epilogue can restore it directly (undoing both
  // the static frame and any dynamic_stackalloc bytes).  $F4 is the
  // saved-SP slot; $F0..$F1 is reserved for i64 return high-half;
  // $E0..$EF is libcall scratch.  TAY around the TSC preserves A
  // (which holds arg0).
  if (HasVLA) {
    BuildMI(MBB, MBBI, DL, TII.get(W65816::TAY));    // save A
    BuildMI(MBB, MBBI, DL, TII.get(W65816::TSC));    // A = SP
    BuildMI(MBB, MBBI, DL, TII.get(W65816::STA_DP)).addImm(0xF4);
    BuildMI(MBB, MBBI, DL, TII.get(W65816::TYA));    // restore A
  }

  if (StackSize > 0) {
    // Cycle math: each PHA is 4 cyc; the TSC-sequence (TAY+TSC+SEC+
    // SBC+TCS+TYA) is 13 cyc total.  N PHAs win on cycles when 4*N <= 13,
    // i.e. up to 3 PHAs (6-byte frame).  At N=4 (8 bytes): 16 cyc PHAs vs
    // 13 cyc TSC-seq → TSC wins.  Threshold at 6 bytes for speed.
    // (Bytes: N PHAs cost N bytes; TSC-seq costs 8 bytes.  We're
    // optimizing for speed per the project directive.)
    if (StackSize <= 6 && (StackSize % 2) == 0) {
      // Small frame: N/2 PHAs.  Preserves A.
      for (uint64_t i = 0; i < StackSize / 2; ++i)
        BuildMI(MBB, MBBI, DL, TII.get(W65816::PHA));
    } else {
      // Larger frame: TAY/TSC/.../TYA bracket.  Preserves A via Y as a
      // temp.
      BuildMI(MBB, MBBI, DL, TII.get(W65816::TAY));
      BuildMI(MBB, MBBI, DL, TII.get(W65816::TSC));
      BuildMI(MBB, MBBI, DL, TII.get(W65816::SEC));
      BuildMI(MBB, MBBI, DL, TII.get(W65816::SBC_Imm16))
          .addImm(StackSize);
      BuildMI(MBB, MBBI, DL, TII.get(W65816::TCS));
      // Frames > 256 bytes can't be addressed via 8-bit `,S` displacement.
      // Capture the post-allocation `S` into $F6/$F7 as a 16-bit DP frame
      // pointer; eliminateFrameIndex routes far accesses through
      // `LDA/STA ($F6),Y` (bank-0 implicit, since the stack is always
      // bank 0).  A holds the new S right after TCS — store it before
      // restoring A from Y.
      // Capture FP into $F6 when:
      //  - frame > 200 bytes (8-bit `,S` disp can't reach far slots), OR
      //  - function has VLAs (DYNAMIC_STACKALLOC shifts S, breaking
      //    static-frame `,s` access).
      if (StackSize > 200 || HasVLA) {
        MF.getInfo<W65816MachineFunctionInfo>()->setUsesDpFP(true);
        BuildMI(MBB, MBBI, DL, TII.get(W65816::STA_DP)).addImm(0xF6);
        // Bank byte at $F8 = 0.  expandFarFI uses `LDA/STA [dp],Y`
        // (long indirect Y, opcodes B7/97) which reads a 24-bit
        // pointer at $F6/$F7/$F8 and ignores DBR.  Without this
        // forced-bank-zero, callers that have switched DBR (e.g.
        // for I/O register access via `pha;plb`) silently corrupt
        // every FP-relative load and store in the callee.
        // sha256_transform exposed this — its 246-byte frame uses
        // FP-rel, and the test driver switched DBR to bank 2 for
        // probe writes before invoking it.
        BuildMI(MBB, MBBI, DL, TII.get(W65816::STZ_DP)).addImm(0xF8);
      }
      BuildMI(MBB, MBBI, DL, TII.get(W65816::TYA));
    }
  }
  // VLA function with no static frame (or PHA-only): still need FP.
  if (HasVLA &&
      !MF.getInfo<W65816MachineFunctionInfo>()->getUsesDpFP()) {
    MF.getInfo<W65816MachineFunctionInfo>()->setUsesDpFP(true);
    BuildMI(MBB, MBBI, DL, TII.get(W65816::TAY));
    BuildMI(MBB, MBBI, DL, TII.get(W65816::TSC));
    BuildMI(MBB, MBBI, DL, TII.get(W65816::STA_DP)).addImm(0xF6);
    BuildMI(MBB, MBBI, DL, TII.get(W65816::STZ_DP)).addImm(0xF8);
    BuildMI(MBB, MBBI, DL, TII.get(W65816::TYA));
  }
}

void W65816FrameLowering::emitEpilogue(MachineFunction &MF,
                                       MachineBasicBlock &MBB) const {
  // Mirror image of the prologue: release any reserved frame bytes
  // before the RTL.
  uint64_t StackSize = MF.getFrameInfo().getStackSize();
  bool HasVLA = MF.getFrameInfo().hasVarSizedObjects();

  const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
  const W65816InstrInfo &TII = *STI.getInstrInfo();
  MachineBasicBlock::iterator MBBI = MBB.getLastNonDebugInstr();
  // Insert before the terminator (the return).
  DebugLoc DL = MBBI != MBB.end() ? MBBI->getDebugLoc() : DebugLoc();

  if (StackSize == 0 && !HasVLA)
    return;

  // Detect whether the return live-out includes Y or X — for i64 returns
  // (Outs[0..2] -> A,X,Y), Y holds bits 32-47 and X holds bits 16-31, so
  // any TAY/PLY/TAX in the SP-restore would corrupt the return value.
  // The RTL terminator carries implicit-uses for every live-out return
  // register; scan them to decide which scratch we can use safely.
  bool YLive = false;
  bool XLive = false;
  if (MBBI != MBB.end() && MBBI->isReturn()) {
    for (const MachineOperand &MO : MBBI->operands()) {
      if (!MO.isReg() || !MO.isImplicit() || !MO.isUse()) continue;
      if (MO.getReg() == W65816::Y) YLive = true;
      else if (MO.getReg() == W65816::X) XLive = true;
    }
  }

  // VLA cleanup: restore entry SP from DP $F4 (saved in prologue).
  // This subsumes BOTH the static frame and any dynamic_stackalloc
  // bytes — we can skip the per-byte PLY/PLA loop entirely.  Preserve
  // A through TAY/TYA since it holds the return value.  For i64
  // returns where Y is also live, route the save through DP $E0
  // ($E0..$EF is libcall scratch — guaranteed dead by epilogue time).
  if (HasVLA) {
    if (YLive) {
      BuildMI(MBB, MBBI, DL, TII.get(W65816::STA_DP)).addImm(0xE0);
      BuildMI(MBB, MBBI, DL, TII.get(W65816::LDA_DP)).addImm(0xF4);
      BuildMI(MBB, MBBI, DL, TII.get(W65816::TCS));
      BuildMI(MBB, MBBI, DL, TII.get(W65816::LDA_DP)).addImm(0xE0);
    } else {
      BuildMI(MBB, MBBI, DL, TII.get(W65816::TAY));
      BuildMI(MBB, MBBI, DL, TII.get(W65816::LDA_DP)).addImm(0xF4);
      BuildMI(MBB, MBBI, DL, TII.get(W65816::TCS));
      BuildMI(MBB, MBBI, DL, TII.get(W65816::TYA));
    }
    return;
  }

  // Prologue is always 16-bit M now (see emitPrologue).  No 8-bit
  // epilogue branch needed.

  // 16-bit M epilogue.  Mirror the prologue: A holds the return value
  // at this point and MUST be preserved.  Small frames release via
  // N/2 PLY (pop into Y, discard); larger frames use
  // TAY/TSC/CLC/ADC #N/TCS/TYA.
  // Mirror the prologue threshold (see comment there).
  if (StackSize <= 6 && (StackSize % 2) == 0 && !YLive) {
    // PLY clobbers Y, which is fine when Y isn't a return reg.
    for (uint64_t i = 0; i < StackSize / 2; ++i)
      BuildMI(MBB, MBBI, DL, TII.get(W65816::PLY));
    return;
  }
  if (YLive) {
    // Y is a return register (i64 / double).  Save A via DP $E0
    // instead of TAY so Y survives.  4 cyc slower than TAY/TYA but
    // correct.  X is allowed to be live too — none of these touch X.
    BuildMI(MBB, MBBI, DL, TII.get(W65816::STA_DP)).addImm(0xE0);
    BuildMI(MBB, MBBI, DL, TII.get(W65816::TSC));
    BuildMI(MBB, MBBI, DL, TII.get(W65816::CLC));
    BuildMI(MBB, MBBI, DL, TII.get(W65816::ADC_Imm16))
        .addImm(StackSize);
    BuildMI(MBB, MBBI, DL, TII.get(W65816::TCS));
    BuildMI(MBB, MBBI, DL, TII.get(W65816::LDA_DP)).addImm(0xE0);
    (void)XLive;
    return;
  }
  BuildMI(MBB, MBBI, DL, TII.get(W65816::TAY));
  BuildMI(MBB, MBBI, DL, TII.get(W65816::TSC));
  BuildMI(MBB, MBBI, DL, TII.get(W65816::CLC));
  BuildMI(MBB, MBBI, DL, TII.get(W65816::ADC_Imm16))
      .addImm(StackSize);
  BuildMI(MBB, MBBI, DL, TII.get(W65816::TCS));
  BuildMI(MBB, MBBI, DL, TII.get(W65816::TYA));
}

MachineBasicBlock::iterator W65816FrameLowering::eliminateCallFramePseudoInstr(
    MachineFunction &MF, MachineBasicBlock &MBB,
    MachineBasicBlock::iterator I) const {
  const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
  const W65816InstrInfo &TII = *STI.getInstrInfo();

  // ADJCALLSTACKDOWN does nothing — we push args via PUSH16/PHA which
  // implicitly decrements SP, so no separate adjustment is needed.
  // ADJCALLSTACKUP releases all the pushed bytes after a call.
  //
  // Critical: A holds the callee's return value here, so this MUST NOT
  // clobber A.  PLY (small-N path) clobbers Y; TAY/.../TYA bracket
  // (large-N path) also clobbers Y.  Both are fine for i8/i16/i32
  // returns but DESTROY the return for i64/double (where X and Y hold
  // mid halves).  Detect i64-return calls by walking back to the JSL
  // and checking implicit-def $x/$y; in that case, save A via DP $E0
  // (libcall scratch, dead by call-up time) so X and Y survive.
  // Caught by `unsigned long long u64add(a,b)` through a noinline
  // boundary returning Y = b_hi (the last popped) instead of the
  // sum's mid-high.
  if (I->getOpcode() == W65816::ADJCALLSTACKUP) {
    int N = I->getOperand(0).getImm();
    if (N > 0) {
      DebugLoc DL = I->getDebugLoc();
      bool YLive = false;
      bool XLive = false;
      // Walk forward looking for COPY %vreg = $x / $y — LowerCall's
      // pattern for materializing return halves.  JSLpseudo's tablegen
      // declares only `Defs=[A]`, so implicit-defs of X/Y aren't on
      // the call op itself.  We have to read what comes after.
      // Stop at the next call (re-clobbers everything) or at any def
      // of X/Y (cancels their post-call value).
      bool Stopped = false;
      for (auto J = std::next(I); J != MBB.end() && !Stopped; ++J) {
        if (J->isCall()) break;
        for (const MachineOperand &MO : J->operands()) {
          if (!MO.isReg()) continue;
          Register R = MO.getReg();
          if (R == W65816::Y) {
            if (MO.isUse()) YLive = true;
            else if (MO.isDef() && !YLive) Stopped = true;
          } else if (R == W65816::X) {
            if (MO.isUse()) XLive = true;
            else if (MO.isDef() && !XLive) Stopped = true;
          }
        }
        if (YLive && XLive) break;
      }
      if (YLive) {
        // i64 return: PLY would eat Y.  Route through DP $E0.  Worth
        // ~4 cyc more than PLY*N/2 but correctness wins.  X is not
        // touched by any of these insns either way, so XLive doesn't
        // change anything here — track it for symmetry.
        BuildMI(MBB, I, DL, TII.get(W65816::STA_DP)).addImm(0xE0);
        BuildMI(MBB, I, DL, TII.get(W65816::TSC));
        BuildMI(MBB, I, DL, TII.get(W65816::CLC));
        BuildMI(MBB, I, DL, TII.get(W65816::ADC_Imm16)).addImm(N);
        BuildMI(MBB, I, DL, TII.get(W65816::TCS));
        BuildMI(MBB, I, DL, TII.get(W65816::LDA_DP)).addImm(0xE0);
        (void)XLive;
      } else if (N <= 14 && (N % 2) == 0) {
        for (int i = 0; i < N / 2; ++i)
          BuildMI(MBB, I, DL, TII.get(W65816::PLY));
      } else {
        BuildMI(MBB, I, DL, TII.get(W65816::TAY));
        BuildMI(MBB, I, DL, TII.get(W65816::TSC));
        BuildMI(MBB, I, DL, TII.get(W65816::CLC));
        BuildMI(MBB, I, DL, TII.get(W65816::ADC_Imm16)).addImm(N);
        BuildMI(MBB, I, DL, TII.get(W65816::TCS));
        BuildMI(MBB, I, DL, TII.get(W65816::TYA));
      }
    }
  }
  return MBB.erase(I);
}
