//===-- LanaiFrameLowering.cpp - Lanai 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the Lanai implementation of TargetFrameLowering class.
//
//===----------------------------------------------------------------------===//

#include "LanaiFrameLowering.h"

#include "LanaiAluCode.h"
#include "LanaiInstrInfo.h"
#include "LanaiSubtarget.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"

using namespace llvm;

// Determines the size of the frame and maximum call frame size.
void LanaiFrameLowering::determineFrameLayout(MachineFunction &MF) const {
  MachineFrameInfo &MFI = MF.getFrameInfo();
  const LanaiRegisterInfo *LRI = STI.getRegisterInfo();

  // Get the number of bytes to allocate from the FrameInfo.
  unsigned FrameSize = MFI.getStackSize();

  // Get the alignment.
  Align StackAlign =
      LRI->hasStackRealignment(MF) ? MFI.getMaxAlign() : getStackAlign();

  // Get the maximum call frame size of all the calls.
  unsigned MaxCallFrameSize = MFI.getMaxCallFrameSize();

  // If we have dynamic alloca then MaxCallFrameSize needs to be aligned so
  // that allocations will be aligned.
  if (MFI.hasVarSizedObjects())
    MaxCallFrameSize = alignTo(MaxCallFrameSize, StackAlign);

  // Update maximum call frame size.
  MFI.setMaxCallFrameSize(MaxCallFrameSize);

  // Include call frame size in total.
  if (!(hasReservedCallFrame(MF) && MFI.adjustsStack()))
    FrameSize += MaxCallFrameSize;

  // Make sure the frame is aligned.
  FrameSize = alignTo(FrameSize, StackAlign);

  // Update frame info.
  MFI.setStackSize(FrameSize);
}

// Iterates through each basic block in a machine function and replaces
// ADJDYNALLOC pseudo instructions with a Lanai:ADDI with the
// maximum call frame size as the immediate.
void LanaiFrameLowering::replaceAdjDynAllocPseudo(MachineFunction &MF) const {
  const LanaiInstrInfo &LII = *STI.getInstrInfo();
  unsigned MaxCallFrameSize = MF.getFrameInfo().getMaxCallFrameSize();

  for (MachineBasicBlock &MBB : MF) {
    for (MachineInstr &MI : llvm::make_early_inc_range(MBB)) {
      if (MI.getOpcode() == Lanai::ADJDYNALLOC) {
        DebugLoc DL = MI.getDebugLoc();
        Register Dst = MI.getOperand(0).getReg();
        Register Src = MI.getOperand(1).getReg();

        BuildMI(MBB, MI, DL, LII.get(Lanai::ADD_I_LO), Dst)
            .addReg(Src)
            .addImm(MaxCallFrameSize);
        MI.eraseFromParent();
      }
    }
  }
}

// Generates the following sequence for function entry:
//   st %fp,-4[*%sp]        !push old FP
//   add %sp,8,%fp          !generate new FP
//   sub %sp,0x4,%sp        !allocate stack space (as needed)
void LanaiFrameLowering::emitPrologue(MachineFunction &MF,
                                      MachineBasicBlock &MBB) const {
  assert(&MF.front() == &MBB && "Shrink-wrapping not yet supported");

  MachineFrameInfo &MFI = MF.getFrameInfo();
  const LanaiInstrInfo &LII = *STI.getInstrInfo();
  MachineBasicBlock::iterator MBBI = MBB.begin();

  // Debug location must be unknown since the first debug location is used
  // to determine the end of the prologue.
  DebugLoc DL;

  // Determine the correct frame layout
  determineFrameLayout(MF);

  // FIXME: This appears to be overallocating.  Needs investigation.
  // Get the number of bytes to allocate from the FrameInfo.
  unsigned StackSize = MFI.getStackSize();

  // Push old FP
  // st %fp,-4[*%sp]
  BuildMI(MBB, MBBI, DL, LII.get(Lanai::SW_RI))
      .addReg(Lanai::FP)
      .addReg(Lanai::SP)
      .addImm(-4)
      .addImm(LPAC::makePreOp(LPAC::ADD))
      .setMIFlag(MachineInstr::FrameSetup);

  // Generate new FP
  // add %sp,8,%fp
  BuildMI(MBB, MBBI, DL, LII.get(Lanai::ADD_I_LO), Lanai::FP)
      .addReg(Lanai::SP)
      .addImm(8)
      .setMIFlag(MachineInstr::FrameSetup);

  // Allocate space on the stack if needed
  // sub %sp,StackSize,%sp
  if (StackSize != 0) {
    BuildMI(MBB, MBBI, DL, LII.get(Lanai::SUB_I_LO), Lanai::SP)
        .addReg(Lanai::SP)
        .addImm(StackSize)
        .setMIFlag(MachineInstr::FrameSetup);
  }

  // Replace ADJDYNANALLOC
  if (MFI.hasVarSizedObjects())
    replaceAdjDynAllocPseudo(MF);
}

MachineBasicBlock::iterator LanaiFrameLowering::eliminateCallFramePseudoInstr(
    MachineFunction & /*MF*/, MachineBasicBlock &MBB,
    MachineBasicBlock::iterator I) const {
  // Discard ADJCALLSTACKDOWN, ADJCALLSTACKUP instructions.
  return MBB.erase(I);
}

// The function epilogue should not depend on the current stack pointer!
// It should use the frame pointer only.  This is mandatory because
// of alloca; we also take advantage of it to omit stack adjustments
// before returning.
//
// Note that when we go to restore the preserved register values we must
// not try to address their slots by using offsets from the stack pointer.
// That's because the stack pointer may have been moved during the function
// execution due to a call to alloca().  Rather, we must restore all
// preserved registers via offsets from the frame pointer value.
//
// Note also that when the current frame is being "popped" (by adjusting
// the value of the stack pointer) on function exit, we must (for the
// sake of alloca) set the new value of the stack pointer based upon
// the current value of the frame pointer.  We can't just add what we
// believe to be the (static) frame size to the stack pointer because
// if we did that, and alloca() had been called during this function,
// we would end up returning *without* having fully deallocated all of
// the space grabbed by alloca.  If that happened, and a function
// containing one or more alloca() calls was called over and over again,
// then the stack would grow without limit!
//
// RET is lowered to
//      ld -4[%fp],%pc  # modify %pc (two delay slots)
// as the return address is in the stack frame and mov to pc is allowed.
// emitEpilogue emits
//      mov %fp,%sp     # restore the stack pointer
//      ld -8[%fp],%fp  # restore the caller's frame pointer
// before RET and the delay slot filler will move RET such that these
// instructions execute in the delay slots of the load to PC.
void LanaiFrameLowering::emitEpilogue(MachineFunction & /*MF*/,
                                      MachineBasicBlock &MBB) const {
  MachineBasicBlock::iterator MBBI = MBB.getLastNonDebugInstr();
  const LanaiInstrInfo &LII = *STI.getInstrInfo();
  DebugLoc DL = MBBI->getDebugLoc();

  // Restore the stack pointer using the callee's frame pointer value.
  BuildMI(MBB, MBBI, DL, LII.get(Lanai::ADD_I_LO), Lanai::SP)
      .addReg(Lanai::FP)
      .addImm(0);

  // Restore the frame pointer from the stack.
  BuildMI(MBB, MBBI, DL, LII.get(Lanai::LDW_RI), Lanai::FP)
      .addReg(Lanai::FP)
      .addImm(-8)
      .addImm(LPAC::ADD);
}

void LanaiFrameLowering::determineCalleeSaves(MachineFunction &MF,
                                              BitVector &SavedRegs,
                                              RegScavenger *RS) const {
  TargetFrameLowering::determineCalleeSaves(MF, SavedRegs, RS);

  MachineFrameInfo &MFI = MF.getFrameInfo();
  const LanaiRegisterInfo *LRI = STI.getRegisterInfo();
  int Offset = -4;

  // Reserve 4 bytes for the saved RCA
  MFI.CreateFixedObject(4, Offset, true);
  Offset -= 4;

  // Reserve 4 bytes for the saved FP
  MFI.CreateFixedObject(4, Offset, true);
  Offset -= 4;

  if (LRI->hasBasePointer(MF)) {
    MFI.CreateFixedObject(4, Offset, true);
    SavedRegs.reset(LRI->getBaseRegister());
  }
}
