//===-- M68kMCCodeEmitter.cpp - Convert M68k code emitter ---*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file contains defintions for M68k code emitter.
///
//===----------------------------------------------------------------------===//

#include "MCTargetDesc/M68kMCCodeEmitter.h"
#include "MCTargetDesc/M68kBaseInfo.h"
#include "MCTargetDesc/M68kFixupKinds.h"
#include "MCTargetDesc/M68kMCTargetDesc.h"

#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

#define DEBUG_TYPE "m68k-mccodeemitter"

namespace {
class M68kMCCodeEmitter : public MCCodeEmitter {
  M68kMCCodeEmitter(const M68kMCCodeEmitter &) = delete;
  void operator=(const M68kMCCodeEmitter &) = delete;
  const MCInstrInfo &MCII;
  MCContext &Ctx;

public:
  M68kMCCodeEmitter(const MCInstrInfo &mcii, MCContext &ctx)
      : MCII(mcii), Ctx(ctx) {}

  ~M68kMCCodeEmitter() override {}

  // TableGen'erated function
  const uint8_t *getGenInstrBeads(const MCInst &MI) const {
    return M68k::getMCInstrBeads(MI.getOpcode());
  }

  unsigned encodeBits(unsigned ThisByte, uint8_t Bead, const MCInst &MI,
                      const MCInstrDesc &Desc, uint64_t &Buffer,
                      unsigned Offset, SmallVectorImpl<MCFixup> &Fixups,
                      const MCSubtargetInfo &STI) const;

  unsigned encodeReg(unsigned ThisByte, uint8_t Bead, const MCInst &MI,
                     const MCInstrDesc &Desc, uint64_t &Buffer, unsigned Offset,
                     SmallVectorImpl<MCFixup> &Fixups,
                     const MCSubtargetInfo &STI) const;

  unsigned encodeImm(unsigned ThisByte, uint8_t Bead, const MCInst &MI,
                     const MCInstrDesc &Desc, uint64_t &Buffer, unsigned Offset,
                     SmallVectorImpl<MCFixup> &Fixups,
                     const MCSubtargetInfo &STI) const;

  void encodeInstruction(const MCInst &MI, raw_ostream &OS,
                         SmallVectorImpl<MCFixup> &Fixups,
                         const MCSubtargetInfo &STI) const override;
};

} // end anonymous namespace

unsigned M68kMCCodeEmitter::encodeBits(unsigned ThisByte, uint8_t Bead,
                                       const MCInst &MI,
                                       const MCInstrDesc &Desc,
                                       uint64_t &Buffer, unsigned Offset,
                                       SmallVectorImpl<MCFixup> &Fixups,
                                       const MCSubtargetInfo &STI) const {
  unsigned Num = 0;
  switch (Bead & 0xF) {
  case M68kBeads::Bits1:
    Num = 1;
    break;
  case M68kBeads::Bits2:
    Num = 2;
    break;
  case M68kBeads::Bits3:
    Num = 3;
    break;
  case M68kBeads::Bits4:
    Num = 4;
    break;
  }
  unsigned char Val = (Bead & 0xF0) >> 4;

  LLVM_DEBUG(dbgs() << "\tEncodeBits"
                    << " Num: " << Num << " Val: 0x");
  LLVM_DEBUG(dbgs().write_hex(Val) << "\n");

  Buffer |= (Val << Offset);

  return Num;
}

unsigned M68kMCCodeEmitter::encodeReg(unsigned ThisByte, uint8_t Bead,
                                      const MCInst &MI, const MCInstrDesc &Desc,
                                      uint64_t &Buffer, unsigned Offset,
                                      SmallVectorImpl<MCFixup> &Fixups,
                                      const MCSubtargetInfo &STI) const {
  bool DA, Reg;
  switch (Bead & 0xF) {
  default:
    llvm_unreachable("Unrecognized Bead code for register type");
  case M68kBeads::DAReg:
    Reg = true;
    DA = true;
    break;
  case M68kBeads::DA:
    Reg = false;
    DA = true;
    break;
  case M68kBeads::DReg:
  case M68kBeads::Reg:
    Reg = true;
    DA = false;
    break;
  }

  unsigned Op = (Bead & 0x70) >> 4;
  bool Alt = (Bead & 0x80);
  LLVM_DEBUG(dbgs() << "\tEncodeReg"
                    << " Op: " << Op << ", DA: " << DA << ", Reg: " << Reg
                    << ", Alt: " << Alt << "\n");

  auto MIOpIdx = M68k::getLogicalOperandIdx(MI.getOpcode(), Op);
  bool IsPCRel = Desc.OpInfo[MIOpIdx].OperandType == MCOI::OPERAND_PCREL;

  MCOperand MCO;
  if (M68kII::hasMultiMIOperands(MI.getOpcode(), Op)) {
    if (IsPCRel) {
      assert(Alt &&
             "PCRel addresses use Alt bead register encoding by default");
      MCO = MI.getOperand(MIOpIdx + M68k::PCRelIndex);
    } else {
      MCO = MI.getOperand(MIOpIdx + (Alt ? M68k::MemIndex : M68k::MemBase));
    }
  } else {
    assert(!Alt && "You cannot use Alt register with a simple operand");
    MCO = MI.getOperand(MIOpIdx);
  }

  unsigned RegNum = MCO.getReg();
  auto RI = Ctx.getRegisterInfo();

  unsigned Written = 0;
  if (Reg) {
    uint32_t Val = RI->getEncodingValue(RegNum);
    Buffer |= (Val & 7) << Offset;
    Offset += 3;
    Written += 3;
  }

  if (DA) {
    Buffer |= (uint64_t)M68kII::isAddressRegister(RegNum) << Offset;
    Written++;
  }

  return Written;
}

static unsigned EmitConstant(uint64_t Val, unsigned Size, unsigned Pad,
                             uint64_t &Buffer, unsigned Offset) {
  assert(Size + Offset <= 64 && isUIntN(Size, Val) && "Value does not fit");

  // Writing Value in host's endianness
  Buffer |= (Val & ((1ULL << Size) - 1)) << Offset;
  return Size + Pad;
}

unsigned M68kMCCodeEmitter::encodeImm(unsigned ThisByte, uint8_t Bead,
                                      const MCInst &MI, const MCInstrDesc &Desc,
                                      uint64_t &Buffer, unsigned Offset,
                                      SmallVectorImpl<MCFixup> &Fixups,
                                      const MCSubtargetInfo &STI) const {
  unsigned ThisWord = ThisByte / 2;
  unsigned Size = 0;
  unsigned Pad = 0;
  unsigned FixOffset = 0;
  int64_t Addendum = 0;
  bool NoExpr = false;

  unsigned Type = Bead & 0xF;
  unsigned Op = (Bead & 0x70) >> 4;
  bool Alt = (Bead & 0x80);

  auto MIOpIdx = M68k::getLogicalOperandIdx(MI.getOpcode(), Op);
  bool IsPCRel = Desc.OpInfo[MIOpIdx].OperandType == MCOI::OPERAND_PCREL;

  // The PC value upon instruction reading of a short jump will point to the
  // next instruction, thus we need to compensate 2 bytes, which is the diff
  // between the patch point and the PC.
  if (IsPCRel && ThisWord == 0)
    Addendum -= 2;

  switch (Type) {
  // ??? what happens if it is not byte aligned
  // ??? is it even possible
  case M68kBeads::Disp8:
    Size = 8;
    Pad = 0;
    FixOffset = ThisByte + 1;
    Addendum += 1;
    break;
  case M68kBeads::Imm8:
    Size = 8;
    Pad = 8;
    FixOffset = ThisByte;
    break;
  case M68kBeads::Imm16:
    Size = 16;
    Pad = 0;
    FixOffset = ThisByte;
    break;
  case M68kBeads::Imm32:
    Size = 32;
    Pad = 0;
    FixOffset = ThisByte;
    break;
  case M68kBeads::Imm3:
    Size = 3;
    Pad = 0;
    NoExpr = true;
    break;
  }

  LLVM_DEBUG(dbgs() << "\tEncodeImm"
                    << " Op: " << Op << ", Size: " << Size << ", Alt: " << Alt
                    << "\n");

  MCOperand MCO;
  if (M68kII::hasMultiMIOperands(MI.getOpcode(), Op)) {

    if (IsPCRel) {
      assert(!Alt && "You cannot use ALT operand with PCRel");
      MCO = MI.getOperand(MIOpIdx + M68k::PCRelDisp);
    } else {
      MCO = MI.getOperand(MIOpIdx + (Alt ? M68k::MemOuter : M68k::MemDisp));
    }

    if (MCO.isExpr()) {
      assert(!NoExpr && "Cannot use expression here");
      const MCExpr *Expr = MCO.getExpr();

      // This only makes sense for PCRel instructions since PC points to the
      // extension word and Disp8 for example is right justified and requires
      // correction. E.g. R_68K_PC32 is calculated as S + A - P, P for Disp8
      // will be EXTENSION_WORD + 1 thus we need to have A equal to 1 to
      // compensate.
      // TODO count extension words
      if (IsPCRel && Addendum != 0) {
        Expr = MCBinaryExpr::createAdd(
            Expr, MCConstantExpr::create(Addendum, Ctx), Ctx);
      }

      Fixups.push_back(MCFixup::create(
          FixOffset, Expr, getFixupForSize(Size, IsPCRel), MI.getLoc()));
      // Write zeros
      return EmitConstant(0, Size, Pad, Buffer, Offset);
    }

  } else {
    MCO = MI.getOperand(MIOpIdx);
    if (MCO.isExpr()) {
      assert(!NoExpr && "Cannot use expression here");
      const MCExpr *Expr = MCO.getExpr();

      if (Addendum != 0) {
        Expr = MCBinaryExpr::createAdd(
            Expr, MCConstantExpr::create(Addendum, Ctx), Ctx);
      }

      Fixups.push_back(MCFixup::create(
          FixOffset, Expr, getFixupForSize(Size, IsPCRel), MI.getLoc()));
      // Write zeros
      return EmitConstant(0, Size, Pad, Buffer, Offset);
    }
  }

  int64_t I = MCO.getImm();

  // Store 8 as 0, thus making range 1-8
  if (Type == M68kBeads::Imm3 && Alt) {
    assert(I && "Cannot encode Alt Imm3 zero value");
    I %= 8;
  } else {
    assert(isIntN(Size, I));
  }

  uint64_t Imm = I;

  // 32 bit Imm requires HI16 first then LO16
  if (Size == 32) {
    Offset += EmitConstant((Imm >> 16) & 0xFFFF, 16, Pad, Buffer, Offset);
    EmitConstant(Imm & 0xFFFF, 16, Pad, Buffer, Offset);
    return Size;
  }

  return EmitConstant(Imm & ((1ULL << Size) - 1), Size, Pad, Buffer, Offset);
}

#include "M68kGenMCCodeBeads.inc"

void M68kMCCodeEmitter::encodeInstruction(const MCInst &MI, raw_ostream &OS,
                                          SmallVectorImpl<MCFixup> &Fixups,
                                          const MCSubtargetInfo &STI) const {
  unsigned Opcode = MI.getOpcode();
  const MCInstrDesc &Desc = MCII.get(Opcode);

  LLVM_DEBUG(dbgs() << "EncodeInstruction: " << MCII.getName(Opcode) << "("
                    << Opcode << ")\n");

  const uint8_t *Beads = getGenInstrBeads(MI);
  if (!Beads || !*Beads) {
    llvm_unreachable("*** Instruction does not have Beads defined");
  }

  uint64_t Buffer = 0;
  unsigned Offset = 0;
  unsigned ThisByte = 0;

  for (uint8_t Bead = *Beads; Bead; Bead = *++Beads) {
    // Check for control beads
    if (!(Bead & 0xF)) {
      switch (Bead >> 4) {
      case M68kBeads::Ignore:
        continue;
      }
    }

    switch (Bead & 0xF) {
    default:
      llvm_unreachable("Unknown Bead code");
      break;
    case M68kBeads::Bits1:
    case M68kBeads::Bits2:
    case M68kBeads::Bits3:
    case M68kBeads::Bits4:
      Offset +=
          encodeBits(ThisByte, Bead, MI, Desc, Buffer, Offset, Fixups, STI);
      break;
    case M68kBeads::DAReg:
    case M68kBeads::DA:
    case M68kBeads::DReg:
    case M68kBeads::Reg:
      Offset +=
          encodeReg(ThisByte, Bead, MI, Desc, Buffer, Offset, Fixups, STI);
      break;
    case M68kBeads::Disp8:
    case M68kBeads::Imm8:
    case M68kBeads::Imm16:
    case M68kBeads::Imm32:
    case M68kBeads::Imm3:
      Offset +=
          encodeImm(ThisByte, Bead, MI, Desc, Buffer, Offset, Fixups, STI);
      break;
    }

    // Since M68k is Big Endian we need to rotate each instruction word
    while (Offset / 16) {
      support::endian::write<uint16_t>(OS, Buffer, support::big);
      Buffer >>= 16;
      Offset -= 16;
      ThisByte += 2;
    }
  }

  assert(Offset == 0 && "M68k Instructions are % 2 bytes");
  assert((ThisByte && !(ThisByte % 2)) && "M68k Instructions are % 2 bytes");
}

MCCodeEmitter *llvm::createM68kMCCodeEmitter(const MCInstrInfo &MCII,
                                             const MCRegisterInfo &MRI,
                                             MCContext &Ctx) {
  return new M68kMCCodeEmitter(MCII, Ctx);
}
