//===- ColossusDisassembler.cpp - Disassembler for Colossus -----*- C++ -*-===//
//    Copyright (c) 2023 Graphcore Ltd. All Rights Reserved.
//     Licensed under the Apache License, Version 2.0 (the "License");
//     you may not use this file except in compliance with the License.
//     You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//     Unless required by applicable law or agreed to in writing, software
//     distributed under the License is distributed on an "AS IS" BASIS,
//     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//     See the License for the specific language governing permissions and
//     limitations under the License.
// --- LLVM Exceptions to the Apache 2.0 License ----
//
// As an exception, if, as a result of your compiling your source code, portions
// of this Software are embedded into an Object form of such source code, you
// may redistribute such embedded portions in such Object form without complying
// with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
//
// In addition, if you combine or link compiled forms of this Software with
// software that is licensed under the GPLv2 ("Combined Software") and if a
// court of competent jurisdiction determines that the patent provision (Section
// 3), the indemnity provision (Section 9) or other Section of the License
// conflicts with the conditions of the GPLv2, you may retroactively and
// prospectively choose to deem waived or otherwise exclude such Section(s) of
// the License, but only in their entirety and only with respect to the Combined
// Software.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file is part of the Colossus Disassembler.
///
//===----------------------------------------------------------------------===//

#include "Colossus.h"
#include "ColossusRegisterInfo.h"
#include "MCTargetDesc/ColossusMCInstrInfo.h"
#include "TargetInfo/ColossusTargetInfo.h"
#include "llvm/CodeGen/TargetOpcodes.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDecoderOps.h"
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Endian.h"

using namespace llvm;

#define DEBUG_TYPE "colossus-disassembler"

// Pull DecodeStatus and its enum values into the global namespace.
typedef MCDisassembler::DecodeStatus DecodeStatus;

// Forward declare these because the autogenerated code will reference them.
// Definitions are further down.
static DecodeStatus DecodeARRegisterClass(llvm::MCInst &Inst,
                                          unsigned RegNo, uint64_t Address,
                                          const void *Decoder);

static DecodeStatus DecodeMRRegisterClass(llvm::MCInst &Inst,
                                          unsigned RegNo, uint64_t Address,
                                          const void *Decoder);

static DecodeStatus DecodeMRPairRegisterClass(llvm::MCInst &Inst,
                                              unsigned RegNo, uint64_t Address,
                                              const void *Decoder);

static DecodeStatus DecodeARPairRegisterClass(llvm::MCInst &Inst,
                                              unsigned RegNo,
                                              uint64_t Address,
                                              const void *Decoder);

static DecodeStatus DecodeARQuadRegisterClass(llvm::MCInst &Inst,
                                              unsigned RegNo,
                                              uint64_t Address,
                                              const void *Decoder);

static DecodeStatus DecodeImm8SIOperand(MCInst &Inst, uint64_t Imm,
                                        int64_t Address, const void *Decoder);

static DecodeStatus DecodeImm12IZOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder);

static DecodeStatus DecodeImm12ZIOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder);

static DecodeStatus DecodeImm16SIOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder);

static DecodeStatus
DecodeMEMrrOperand(MCInst &Inst, uint64_t Imm,
                   int64_t Address, const void *Decoder);

static DecodeStatus
DecodeMEMrrrOperand(MCInst &Inst, uint64_t Imm,
                    int64_t Address, const void *Decoder);

static DecodeStatus
DecodeMEMrriOperand(MCInst &Inst, uint64_t Imm,
                    int64_t Address, const void *Decoder);

namespace {

/// \brief A disassembler class for Colossus.
class ColossusDisassembler : public MCDisassembler {
private:
  mutable MCInst BundleInstruction[MAX_NUM_INSTRUCTIONS_IN_BUNDLE];

public:
  ColossusDisassembler(const MCSubtargetInfo &STI, MCContext &Ctx) :
    MCDisassembler(STI, Ctx) {}

  // Warning: when processing bundle instructions, this function uses
  // internal state to record multiple child instructions. Therefore, the
  // 'Instr' returned from this function must be processed before subsequent
  // calls. For example, this will fail:
  //   getInstruction(a,...); getInstruction(b,...); printInst(a,...);
  // To get around this limitation callers could make a copy of the child
  // instructions within the bundle, and update 'Instr' with the local
  // copies.
  DecodeStatus getInstruction(MCInst &Instr, uint64_t &Size,
                              ArrayRef<uint8_t> Bytes, uint64_t Address,
                              raw_ostream &CStream) const override;

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

template <class T>
static bool
readInstruction(ArrayRef<uint8_t> Bytes, uint64_t Address, uint64_t &Size,
                T &Insn) {
  if (Bytes.size() < sizeof(T)) {
    Size = 0;
    return false;
  }

  Insn = llvm::support::endian::read<T, llvm::support::little,
         llvm::support::unaligned>(Bytes.data());

  return true;
}

#include "ColossusGenDisassemblerTables.inc"

static unsigned getReg(const void *D, unsigned RC, unsigned RegNo) {
  auto Dis = static_cast<const ColossusDisassembler*>(D);
  const MCRegisterInfo *RegInfo = Dis->getContext().getRegisterInfo();
  return *(RegInfo->getRegClass(RC).begin() + RegNo);
}

static DecodeStatus DecodeMRRegisterClass(MCInst &Inst,
                                          unsigned RegNo,
                                          uint64_t Address,
                                          const void *Decoder) {
  auto Dis = static_cast<const ColossusDisassembler*>(Decoder);
  const MCRegisterInfo *RegInfo = Dis->getContext().getRegisterInfo();

  if (RegNo >= RegInfo->getRegClass(Colossus::MRRegClassID).RegsSize)
    return MCDisassembler::Fail;

  unsigned Reg = getReg(Decoder, Colossus::MRRegClassID, RegNo);
  Inst.addOperand(MCOperand::createReg(Reg));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeMRPairRegisterClass(MCInst &Inst,
                                              unsigned RegNo,
                                              uint64_t Address,
                                              const void *Decoder) {
  static const uint16_t RegPairDecoderTable[] = {
      Colossus::MD0, Colossus::MD1, Colossus::MD2, Colossus::MD3, Colossus::MD4,
      Colossus::MD5
  };

  if (RegNo >= array_lengthof(RegPairDecoderTable) * 2 || RegNo & 1)
    return MCDisassembler::Fail;

  Inst.addOperand(MCOperand::createReg(RegPairDecoderTable[RegNo >> 1]));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeARRegisterClass(MCInst &Inst,
                                          unsigned RegNo,
                                          uint64_t Address,
                                          const void *Decoder) {
  auto Dis = static_cast<const ColossusDisassembler*>(Decoder);
  const MCRegisterInfo *RegInfo = Dis->getContext().getRegisterInfo();

  if (RegNo >= RegInfo->getRegClass(Colossus::ARRegClassID).RegsSize)
    return MCDisassembler::Fail;

  unsigned Reg = getReg(Decoder, Colossus::ARRegClassID, RegNo);
  Inst.addOperand(MCOperand::createReg(Reg));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeARPairRegisterClass(MCInst &Inst,
                                              unsigned RegNo,
                                              uint64_t Address,
                                              const void *Decoder) {
  static const uint16_t RegPairDecoderTable[] = {
      Colossus::AD0,  Colossus::AD1,  Colossus::AD2,  Colossus::AD3,
      Colossus::AD4,  Colossus::AD5,  Colossus::AD6,  Colossus::AD7,
      Colossus::AD8,  Colossus::AD9,  Colossus::AD10, Colossus::AD11,
      Colossus::AD12, Colossus::AD13, Colossus::AD14, Colossus::AZEROS};

    if (RegNo & 1) {
      return MCDisassembler::Fail;
    }
    RegNo = RegNo == 14 ? 15 : RegNo >> 1;

    if (RegNo >= array_lengthof(RegPairDecoderTable)) {
      return MCDisassembler::Fail;
  }
  Inst.addOperand(MCOperand::createReg(RegPairDecoderTable[RegNo]));
  return MCDisassembler::Success;
}

static DecodeStatus DecodeARQuadRegisterClass(MCInst &Inst,
                                              unsigned RegNo,
                                              uint64_t Address,
                                              const void *Decoder) {
  static const uint16_t RegQuadDecoderTable[] = {
      Colossus::AQ0,  Colossus::AQ1,  Colossus::AQ2,  Colossus::AQ3,
      Colossus::AQ4,  Colossus::AQ5,  Colossus::AQ6,  Colossus::AQ7,
      Colossus::AQ8,  Colossus::AQ9,  Colossus::AQ10, Colossus::AQ11,
      Colossus::AQ12, Colossus::AQ13, Colossus::AQ14, Colossus::AQZERO};

    if (RegNo & 3) {
      return MCDisassembler::Fail;
    }

    RegNo = RegNo == 12 ? 15 : RegNo >> 2;

  Inst.addOperand(MCOperand::createReg(RegQuadDecoderTable[RegNo]));
  return MCDisassembler::Success;
}

namespace {
  template <unsigned N>
  static DecodeStatus DecodeImmZIOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder) {
    Inst.addOperand(MCOperand::createImm(Imm));
    return MCDisassembler::Success;
  }
  template <unsigned N>
  static DecodeStatus DecodeImmSIOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder) {
    Inst.addOperand(MCOperand::createImm(SignExtend64<N>(Imm)));
    return MCDisassembler::Success;
  }
  template <unsigned N>
  static DecodeStatus DecodeImmIZOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder) {
    Inst.addOperand(MCOperand::createImm(Imm << (32 - N)));
    return MCDisassembler::Success;
  }
}

static DecodeStatus DecodeImm8SIOperand(MCInst &Inst, uint64_t Imm,
                                        int64_t Address, const void *Decoder) {
  return DecodeImmSIOperand<8>(Inst, Imm, Address, Decoder);
}

static DecodeStatus DecodeImm12IZOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder) {
  return DecodeImmIZOperand<12>(Inst, Imm, Address, Decoder);
}

static DecodeStatus DecodeImm12ZIOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder) {
  return DecodeImmZIOperand<12>(Inst, Imm, Address, Decoder);
}

static DecodeStatus DecodeImm16SIOperand(MCInst &Inst, uint64_t Imm,
                                         int64_t Address, const void *Decoder) {
  return DecodeImmSIOperand<16>(Inst, Imm, Address, Decoder);
}

static DecodeStatus
DecodeMEMrrOperand(MCInst &Inst, uint64_t Imm,
                   int64_t Address, const void *Decoder) {
  // Decode an MEMrr field, 4 bit base and offset registers.
  // offset[7:4], base[3:0]
  uint64_t BaseReg = Imm & 0xF;
  DecodeStatus Ret = DecodeMRRegisterClass(Inst, BaseReg, Address, Decoder);
  if (Ret == MCDisassembler::Fail) {
    return MCDisassembler::Fail;
  }
  uint64_t OffsetReg = (Imm >> 4) & 0xF;
  Ret = DecodeMRRegisterClass(Inst, OffsetReg, Address, Decoder);
  if (Ret == MCDisassembler::Fail) {
    return MCDisassembler::Fail;
  }
  return MCDisassembler::Success;
}

static DecodeStatus
DecodeMEMrrrOperand(MCInst &Inst, uint64_t Imm,
                    int64_t Address, const void *Decoder) {
  // Decode an MEMrrr field, 4-bit base, delta and offset registers.
  // offset[11:8], delta[7:4], base[3:0]
  uint64_t BaseReg = Imm & 0xF;
  DecodeStatus Ret = DecodeMRRegisterClass(Inst, BaseReg, Address, Decoder);
  if (Ret == MCDisassembler::Fail) {
    return MCDisassembler::Fail;
  }
  uint64_t DeltaReg = (Imm >> 4) & 0xF;
  Ret = DecodeMRRegisterClass(Inst, DeltaReg, Address, Decoder);
  if (Ret == MCDisassembler::Fail) {
    return MCDisassembler::Fail;
  }
  uint64_t OffsetReg = (Imm >> 8) & 0xF;
  Ret = DecodeMRRegisterClass(Inst, OffsetReg, Address, Decoder);
  if (Ret == MCDisassembler::Fail) {
    return MCDisassembler::Fail;
  }
  return MCDisassembler::Success;
}

static DecodeStatus
DecodeMEMrriOperand(MCInst &Inst, uint64_t Imm,
                    int64_t Address, const void *Decoder) {
  // Decode MEMrri field, 4 bit base and delta regs 12 bit signed imm offset.
  // offset[19:8], delta[7:4], base[3:0]
  uint64_t BaseReg = Imm & 0xF;
  DecodeStatus Ret = DecodeMRRegisterClass(Inst, BaseReg, Address, Decoder);
  if (Ret == MCDisassembler::Fail) {
    return MCDisassembler::Fail;
  }
  uint64_t DeltaReg = (Imm >> 4) & 0xF;
  Ret = DecodeMRRegisterClass(Inst, DeltaReg, Address, Decoder);
  if (Ret == MCDisassembler::Fail) {
    return MCDisassembler::Fail;
  }
  uint64_t Offset = (Imm >> 8) & 0xFFF;
  Ret = DecodeImm12ZIOperand(Inst, Offset, Address, Decoder);
  if (Ret == MCDisassembler::Fail) {
    return MCDisassembler::Fail;
  }
  return MCDisassembler::Success;
}

MCDisassembler::DecodeStatus ColossusDisassembler::getSingleInstruction(
    MCInst &Instr, bool &IsCoIssue, uint64_t &Size, ArrayRef<uint8_t> Bytes,
    uint64_t Address, raw_ostream &cStream) const {

  uint32_t Insn32;
  if (!readInstruction(Bytes, Address, Size, Insn32)) {
    return Fail;
  }

  DecodeStatus Result = Fail;

  if (Result == Fail) {
    Result =
        decodeInstruction(DecoderTable32, Instr, Insn32, Address, this, STI);
  }

  if (Result == Fail) {
    auto STICopy = getContext().getSubtargetCopy(STI);
    STICopy.ToggleFeature(Colossus::ModeSupervisor);
    Result = decodeInstruction(DecoderTable32, Instr, Insn32, Address, this, STICopy);
  }

  if (Result != Fail) {
    IsCoIssue = ColossusMCInstrInfo::isCoIssue(Instr, Insn32);
    Size = 4;
    return Result;
  }

  return Fail;
}

MCDisassembler::DecodeStatus
ColossusDisassembler::getInstruction(MCInst &Instr, uint64_t &Size,
                                     ArrayRef<uint8_t> Bytes, uint64_t Address,
                                     raw_ostream &cStream) const {

  bool IsCoIssue;
  DecodeStatus Result =
      getSingleInstruction(Instr, IsCoIssue, Size, Bytes, Address, cStream);

  if (Result != Fail) {
    // If the instruction has the coissue bit set then build up a bundle,
    // and read the next instruction into it.
    if (Size == 4 && IsCoIssue) {
      BundleInstruction[0] = Instr;

      Instr.clear();
      Instr.setOpcode(TargetOpcode::BUNDLE);
      Instr.addOperand(MCOperand::createInst(&BundleInstruction[0]));

      Bytes = Bytes.slice(Size);
      BundleInstruction[1].clear();
      Result = getSingleInstruction(BundleInstruction[1], IsCoIssue, Size,
                                    Bytes, Address, cStream);
      if (Result == Fail || Size != 4)
        return Fail;
      Instr.addOperand(MCOperand::createInst(&BundleInstruction[1]));
      Size += 4;
    }

    return Result;
  }

  return Fail;
}

static MCDisassembler *createColossusDisassembler(const Target &T,
                                                  const MCSubtargetInfo &STI,
                                                  MCContext &Ctx) {
  return new ColossusDisassembler(STI, Ctx);
}

extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeColossusDisassembler() {
  // Register the disassembler.
  TargetRegistry::RegisterMCDisassembler(getTheColossusTarget(),
                                         createColossusDisassembler);
}
