//===- ARCDisassembler.cpp - Disassembler for ARC ---------------*- 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 is part of the ARC Disassembler.
///
//===----------------------------------------------------------------------===//

#include "ARC.h"
#include "ARCRegisterInfo.h"
#include "MCTargetDesc/ARCMCTargetDesc.h"
#include "TargetInfo/ARCTargetInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCFixedLenDisassembler.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"

using namespace llvm;

#define DEBUG_TYPE "arc-disassembler"

using DecodeStatus = MCDisassembler::DecodeStatus;

namespace {

/// A disassembler class for ARC.
class ARCDisassembler : public MCDisassembler {
public:
  std::unique_ptr<MCInstrInfo const> const MCII;

  ARCDisassembler(const MCSubtargetInfo &STI, MCContext &Ctx,
                  MCInstrInfo const *MCII)
      : MCDisassembler(STI, Ctx), MCII(MCII) {}

  DecodeStatus getInstruction(MCInst &Instr, uint64_t &Size,
                              ArrayRef<uint8_t> Bytes, uint64_t Address,
                              raw_ostream &CStream) const override;
};

} // end anonymous namespace

static bool readInstruction32(ArrayRef<uint8_t> Bytes, uint64_t Address,
                              uint64_t &Size, uint32_t &Insn) {
  Size = 4;
  // Read 2 16-bit values, but swap hi/lo parts.
  Insn =
      (Bytes[0] << 16) | (Bytes[1] << 24) | (Bytes[2] << 0) | (Bytes[3] << 8);
  return true;
}

static bool readInstruction64(ArrayRef<uint8_t> Bytes, uint64_t Address,
                              uint64_t &Size, uint64_t &Insn) {
  Size = 8;
  Insn = ((uint64_t)Bytes[0] << 16) | ((uint64_t)Bytes[1] << 24) |
         ((uint64_t)Bytes[2] << 0) | ((uint64_t)Bytes[3] << 8) |
         ((uint64_t)Bytes[4] << 48) | ((uint64_t)Bytes[5] << 56) |
         ((uint64_t)Bytes[6] << 32) | ((uint64_t)Bytes[7] << 40);
  return true;
}

static bool readInstruction48(ArrayRef<uint8_t> Bytes, uint64_t Address,
                              uint64_t &Size, uint64_t &Insn) {
  Size = 6;
  Insn = ((uint64_t)Bytes[0] << 0) | ((uint64_t)Bytes[1] << 8) |
         ((uint64_t)Bytes[2] << 32) | ((uint64_t)Bytes[3] << 40) |
         ((uint64_t)Bytes[4] << 16) | ((uint64_t)Bytes[5] << 24);
  return true;
}

static bool readInstruction16(ArrayRef<uint8_t> Bytes, uint64_t Address,
                              uint64_t &Size, uint32_t &Insn) {
  Size = 2;
  Insn = (Bytes[0] << 0) | (Bytes[1] << 8);
  return true;
}

template <unsigned B>
static DecodeStatus DecodeSignedOperand(MCInst &Inst, unsigned InsnS,
                                        uint64_t Address = 0,
                                        const void *Decoder = nullptr);

template <unsigned B>
static DecodeStatus DecodeFromCyclicRange(MCInst &Inst, unsigned InsnS,
                                        uint64_t Address = 0,
                                        const void *Decoder = nullptr);

template <unsigned B>
static DecodeStatus DecodeBranchTargetS(MCInst &Inst, unsigned InsnS,
                                        uint64_t Address, const void *Decoder);

static DecodeStatus DecodeMEMrs9(MCInst &, unsigned, uint64_t, const void *);

static DecodeStatus DecodeLdLImmInstruction(MCInst &, uint64_t, uint64_t,
                                            const void *);

static DecodeStatus DecodeStLImmInstruction(MCInst &, uint64_t, uint64_t,
                                            const void *);

static DecodeStatus DecodeLdRLImmInstruction(MCInst &, uint64_t, uint64_t,
                                             const void *);

static DecodeStatus DecodeSOPwithRS12(MCInst &, uint64_t, uint64_t,
                                      const void *);

static DecodeStatus DecodeSOPwithRU6(MCInst &, uint64_t, uint64_t,
                                     const void *);

static DecodeStatus DecodeCCRU6Instruction(MCInst &, uint64_t, uint64_t,
                                           const void *);

static DecodeStatus DecodeMoveHRegInstruction(MCInst &Inst, uint64_t, uint64_t,
                                              const void *);

static const uint16_t GPR32DecoderTable[] = {
    ARC::R0,  ARC::R1,    ARC::R2,  ARC::R3,   ARC::R4,  ARC::R5,  ARC::R6,
    ARC::R7,  ARC::R8,    ARC::R9,  ARC::R10,  ARC::R11, ARC::R12, ARC::R13,
    ARC::R14, ARC::R15,   ARC::R16, ARC::R17,  ARC::R18, ARC::R19, ARC::R20,
    ARC::R21, ARC::R22,   ARC::R23, ARC::R24,  ARC::R25, ARC::GP,  ARC::FP,
    ARC::SP,  ARC::ILINK, ARC::R30, ARC::BLINK};

static DecodeStatus DecodeGPR32RegisterClass(MCInst &Inst, unsigned RegNo,
                                             uint64_t Address,
                                             const void *Decoder) {
  if (RegNo >= 32) {
    LLVM_DEBUG(dbgs() << "Not a GPR32 register.");
    return MCDisassembler::Fail;
  }

  unsigned Reg = GPR32DecoderTable[RegNo];
  Inst.addOperand(MCOperand::createReg(Reg));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeGBR32ShortRegister(MCInst &Inst, unsigned RegNo,
                                               uint64_t Address,
                                               const void *Decoder) {
  // Enumerates registers from ranges [r0-r3],[r12-r15].
  if (RegNo > 3)
    RegNo += 8; // 4 for r12, etc...

  return DecodeGPR32RegisterClass(Inst, RegNo, Address, Decoder);
}

#include "ARCGenDisassemblerTables.inc"

static unsigned decodeCField(unsigned Insn) {
  return fieldFromInstruction(Insn, 6, 6);
}

static unsigned decodeBField(unsigned Insn) {
  return (fieldFromInstruction(Insn, 12, 3) << 3) |
         fieldFromInstruction(Insn, 24, 3);
}

static unsigned decodeAField(unsigned Insn) {
  return fieldFromInstruction(Insn, 0, 6);
}

static DecodeStatus DecodeMEMrs9(MCInst &Inst, unsigned Insn, uint64_t Address,
                                 const void *Dec) {
  // We have the 9-bit immediate in the low bits, 6-bit register in high bits.
  unsigned S9 = Insn & 0x1ff;
  unsigned R = (Insn & (0x7fff & ~0x1ff)) >> 9;
  DecodeGPR32RegisterClass(Inst, R, Address, Dec);
  Inst.addOperand(MCOperand::createImm(SignExtend32<9>(S9)));
  return MCDisassembler::Success;
}

static bool DecodeSymbolicOperand(MCInst &Inst, uint64_t Address,
                                  uint64_t Value, const void *Decoder) {
  static const uint64_t AtLeast = 2;
  // TODO: Try to force emitter to use MCDisassembler* instead of void*.
  auto Disassembler = static_cast<const MCDisassembler *>(Decoder);
  return (nullptr != Disassembler &&
          Disassembler->tryAddingSymbolicOperand(Inst, Value, Address, true, 0,
                                                 AtLeast));
}

static void DecodeSymbolicOperandOff(MCInst &Inst, uint64_t Address,
                                     uint64_t Offset, const void *Decoder) {
  uint64_t NextAddress = Address + Offset;

  if (!DecodeSymbolicOperand(Inst, Address, NextAddress, Decoder))
    Inst.addOperand(MCOperand::createImm(Offset));
}

template <unsigned B>
static DecodeStatus DecodeBranchTargetS(MCInst &Inst, unsigned InsnS,
                                        uint64_t Address, const void *Decoder) {

  static_assert(B > 0, "field is empty");
  DecodeSymbolicOperandOff(Inst, Address, SignExtend32<B>(InsnS), Decoder);
  return MCDisassembler::Success;
}

template <unsigned B>
static DecodeStatus DecodeSignedOperand(MCInst &Inst, unsigned InsnS,
                                        uint64_t /*Address*/,
                                        const void * /*Decoder*/) {

  static_assert(B > 0, "field is empty");
  Inst.addOperand(MCOperand::createImm(
      SignExtend32<B>(maskTrailingOnes<decltype(InsnS)>(B) & InsnS)));
  return MCDisassembler::Success;
}

template <unsigned B>
static DecodeStatus DecodeFromCyclicRange(MCInst &Inst, unsigned InsnS,
                                          uint64_t /*Address*/,
                                          const void * /*Decoder*/) {

  static_assert(B > 0, "field is empty");
  const unsigned max = (1u << B) - 1;
  Inst.addOperand(
      MCOperand::createImm(InsnS < max ? static_cast<int>(InsnS) : -1));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeStLImmInstruction(MCInst &Inst, uint64_t Insn,
                                            uint64_t Address,
                                            const void *Decoder) {
  unsigned SrcC, DstB, LImm;
  DstB = decodeBField(Insn);
  if (DstB != 62) {
    LLVM_DEBUG(dbgs() << "Decoding StLImm found non-limm register.");
    return MCDisassembler::Fail;
  }
  SrcC = decodeCField(Insn);
  DecodeGPR32RegisterClass(Inst, SrcC, Address, Decoder);
  LImm = (Insn >> 32);
  Inst.addOperand(MCOperand::createImm(LImm));
  Inst.addOperand(MCOperand::createImm(0));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeLdLImmInstruction(MCInst &Inst, uint64_t Insn,
                                            uint64_t Address,
                                            const void *Decoder) {
  unsigned DstA, SrcB, LImm;
  LLVM_DEBUG(dbgs() << "Decoding LdLImm:\n");
  SrcB = decodeBField(Insn);
  if (SrcB != 62) {
    LLVM_DEBUG(dbgs() << "Decoding LdLImm found non-limm register.");
    return MCDisassembler::Fail;
  }
  DstA = decodeAField(Insn);
  DecodeGPR32RegisterClass(Inst, DstA, Address, Decoder);
  LImm = (Insn >> 32);
  Inst.addOperand(MCOperand::createImm(LImm));
  Inst.addOperand(MCOperand::createImm(0));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeLdRLImmInstruction(MCInst &Inst, uint64_t Insn,
                                             uint64_t Address,
                                             const void *Decoder) {
  unsigned DstA, SrcB;
  LLVM_DEBUG(dbgs() << "Decoding LdRLimm\n");
  DstA = decodeAField(Insn);
  DecodeGPR32RegisterClass(Inst, DstA, Address, Decoder);
  SrcB = decodeBField(Insn);
  DecodeGPR32RegisterClass(Inst, SrcB, Address, Decoder);
  if (decodeCField(Insn) != 62) {
    LLVM_DEBUG(dbgs() << "Decoding LdRLimm found non-limm register.");
    return MCDisassembler::Fail;
  }
  Inst.addOperand(MCOperand::createImm((uint32_t)(Insn >> 32)));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeMoveHRegInstruction(MCInst &Inst, uint64_t Insn,
                                              uint64_t Address,
                                              const void *Decoder) {
  LLVM_DEBUG(dbgs() << "Decoding MOV_S h-register\n");
  using Field = decltype(Insn);
  Field H = fieldFromInstruction(Insn, 5, 3) |
            (fieldFromInstruction(Insn, 0, 2) << 3);
  Field G = fieldFromInstruction(Insn, 8, 3) |
            (fieldFromInstruction(Insn, 3, 2) << 3);

  auto DecodeRegisterOrImm = [&Inst, Address, Decoder](Field RegNum,
                                                       Field Value) {
    if (30 == RegNum) {
      Inst.addOperand(MCOperand::createImm(Value));
      return MCDisassembler::Success;
    }

    return DecodeGPR32RegisterClass(Inst, RegNum, Address, Decoder);
  };

  if (MCDisassembler::Success != DecodeRegisterOrImm(G, 0))
    return MCDisassembler::Fail;

  return DecodeRegisterOrImm(H, Insn >> 16u);
}

static DecodeStatus DecodeCCRU6Instruction(MCInst &Inst, uint64_t Insn,
                                           uint64_t Address,
                                           const void *Decoder) {
  unsigned DstB;
  LLVM_DEBUG(dbgs() << "Decoding CCRU6 instruction:\n");
  DstB = decodeBField(Insn);
  DecodeGPR32RegisterClass(Inst, DstB, Address, Decoder);
  using Field = decltype(Insn);
  Field U6Field = fieldFromInstruction(Insn, 6, 6);
  Inst.addOperand(MCOperand::createImm(U6Field));
  Field CCField = fieldFromInstruction(Insn, 0, 4);
  Inst.addOperand(MCOperand::createImm(CCField));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeSOPwithRU6(MCInst &Inst, uint64_t Insn,
                                     uint64_t Address, const void *Decoder) {
  unsigned DstB = decodeBField(Insn);
  DecodeGPR32RegisterClass(Inst, DstB, Address, Decoder);
  using Field = decltype(Insn);
  Field U6 = fieldFromInstruction(Insn, 6, 6);
  Inst.addOperand(MCOperand::createImm(U6));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeSOPwithRS12(MCInst &Inst, uint64_t Insn,
                                      uint64_t Address, const void *Decoder) {
  unsigned DstB = decodeBField(Insn);
  DecodeGPR32RegisterClass(Inst, DstB, Address, Decoder);
  using Field = decltype(Insn);
  Field Lower = fieldFromInstruction(Insn, 6, 6);
  Field Upper = fieldFromInstruction(Insn, 0, 5);
  Field Sign = fieldFromInstruction(Insn, 5, 1) ? -1 : 1;
  Field Result = Sign * ((Upper << 6) + Lower);
  Inst.addOperand(MCOperand::createImm(Result));
  return MCDisassembler::Success;
}

DecodeStatus ARCDisassembler::getInstruction(MCInst &Instr, uint64_t &Size,
                                             ArrayRef<uint8_t> Bytes,
                                             uint64_t Address,
                                             raw_ostream &cStream) const {
  MCDisassembler::DecodeStatus Result;
  if (Bytes.size() < 2) {
    Size = 0;
    return Fail;
  }
  uint8_t DecodeByte = (Bytes[1] & 0xF7) >> 3;
  // 0x00 -> 0x07 are 32-bit instructions.
  // 0x08 -> 0x1F are 16-bit instructions.
  if (DecodeByte < 0x08) {
    // 32-bit instruction.
    if (Bytes.size() < 4) {
      // Did we decode garbage?
      Size = 0;
      return Fail;
    }
    if (Bytes.size() >= 8) {
      // Attempt to decode 64-bit instruction.
      uint64_t Insn64;
      if (!readInstruction64(Bytes, Address, Size, Insn64))
        return Fail;
      Result =
          decodeInstruction(DecoderTable64, Instr, Insn64, Address, this, STI);
      if (Success == Result) {
        LLVM_DEBUG(dbgs() << "Successfully decoded 64-bit instruction.");
        return Result;
      }
      LLVM_DEBUG(dbgs() << "Not a 64-bit instruction, falling back to 32-bit.");
    }
    uint32_t Insn32;
    if (!readInstruction32(Bytes, Address, Size, Insn32)) {
      return Fail;
    }
    // Calling the auto-generated decoder function.
    return decodeInstruction(DecoderTable32, Instr, Insn32, Address, this, STI);
  } else {
    if (Bytes.size() >= 6) {
      // Attempt to treat as instr. with limm data.
      uint64_t Insn48;
      if (!readInstruction48(Bytes, Address, Size, Insn48))
        return Fail;
      Result =
          decodeInstruction(DecoderTable48, Instr, Insn48, Address, this, STI);
      if (Success == Result) {
        LLVM_DEBUG(
            dbgs() << "Successfully decoded 16-bit instruction with limm.");
        return Result;
      }
      LLVM_DEBUG(
          dbgs() << "Not a 16-bit instruction with limm, try without it.");
    }

    uint32_t Insn16;
    if (!readInstruction16(Bytes, Address, Size, Insn16))
      return Fail;

    // Calling the auto-generated decoder function.
    return decodeInstruction(DecoderTable16, Instr, Insn16, Address, this, STI);
  }
}

static MCDisassembler *createARCDisassembler(const Target &T,
                                             const MCSubtargetInfo &STI,
                                             MCContext &Ctx) {
  return new ARCDisassembler(STI, Ctx, T.createMCInstrInfo());
}

extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeARCDisassembler() {
  // Register the disassembler.
  TargetRegistry::RegisterMCDisassembler(getTheARCTarget(),
                                         createARCDisassembler);
}
