//===- llvm/unittest/unittests/MC/AMDGPU/Disassembler.cpp -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "llvm-c/Disassembler.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCDisassembler/MCSymbolizer.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/MCTargetOptions.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/TargetSelect.h"
#include "gtest/gtest.h"

using namespace llvm;

static const char *symbolLookupCallback(void *DisInfo, uint64_t ReferenceValue,
                                        uint64_t *ReferenceType,
                                        uint64_t ReferencePC,
                                        const char **ReferenceName) {
  *ReferenceType = LLVMDisassembler_ReferenceType_InOut_None;
  return nullptr;
}

static const char *TripleName = "amdgcn--amdpal";
static const char *CPUName = "gfx1030";

// Basic smoke test.
TEST(AMDGPUDisassembler, Basic) {
  LLVMInitializeAMDGPUTargetInfo();
  LLVMInitializeAMDGPUTargetMC();
  LLVMInitializeAMDGPUDisassembler();

  uint8_t Bytes[] = {0x04, 0x00, 0x80, 0xb0};
  uint8_t *BytesP = Bytes;
  const char OutStringSize = 100;
  char OutString[OutStringSize];
  LLVMDisasmContextRef DCR = LLVMCreateDisasmCPU(
      TripleName, CPUName, nullptr, 0, nullptr, symbolLookupCallback);

  // Skip test if AMDGPU not built.
  if (!DCR)
    GTEST_SKIP();

  size_t InstSize;
  unsigned NumBytes = sizeof(Bytes);
  unsigned PC = 0U;

  InstSize = LLVMDisasmInstruction(DCR, BytesP, NumBytes, PC, OutString,
                                   OutStringSize);
  EXPECT_EQ(InstSize, 4U);
  EXPECT_EQ(StringRef(OutString), "\ts_version UC_VERSION_GFX10");

  LLVMDisasmDispose(DCR);
}

// Check multiple disassemblers in same MCContext.
TEST(AMDGPUDisassembler, MultiDisassembler) {
  LLVMInitializeAMDGPUTargetInfo();
  LLVMInitializeAMDGPUTargetMC();
  LLVMInitializeAMDGPUDisassembler();

  std::string Error;
  const Target *TheTarget = TargetRegistry::lookupTarget(TripleName, Error);

  // Skip test if AMDGPU not built.
  if (!TheTarget)
    GTEST_SKIP();

  std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(TripleName));
  std::unique_ptr<MCAsmInfo> MAI(
      TheTarget->createMCAsmInfo(*MRI, TripleName, MCTargetOptions()));
  std::unique_ptr<const MCInstrInfo> MII(TheTarget->createMCInstrInfo());
  std::unique_ptr<MCSubtargetInfo> STI(
      TheTarget->createMCSubtargetInfo(TripleName, CPUName, ""));
  auto Ctx = std::make_unique<MCContext>(Triple(TripleName), MAI.get(),
                                         MRI.get(), STI.get());

  int AsmPrinterVariant = MAI->getAssemblerDialect();
  std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter(
      Triple(TripleName), AsmPrinterVariant, *MAI, *MII, *MRI));

  SmallVector<char, 64> InsnStr, AnnoStr;
  raw_svector_ostream OS(InsnStr);
  raw_svector_ostream Annotations(AnnoStr);
  formatted_raw_ostream FormattedOS(OS);

  char StrBuffer[128];

  uint8_t Bytes[] = {0x04, 0x00, 0x80, 0xb0};
  uint64_t InstSize = 0U;
  MCInst Inst1, Inst2;
  MCDisassembler::DecodeStatus Status;

  // Test disassembler works as expected.
  AnnoStr.clear();
  InsnStr.clear();
  std::unique_ptr<MCDisassembler> DisAsm1(
      TheTarget->createMCDisassembler(*STI, *Ctx));
  Status = DisAsm1->getInstruction(Inst1, InstSize, Bytes, 0, Annotations);
  ASSERT_TRUE(Status == MCDisassembler::Success);
  EXPECT_EQ(InstSize, 4U);

  IP->printInst(&Inst1, 0U, Annotations.str(), *STI, FormattedOS);
  ASSERT_TRUE(InsnStr.size() < (sizeof(StrBuffer) - 1));
  std::memcpy(StrBuffer, InsnStr.data(), InsnStr.size());
  StrBuffer[InsnStr.size()] = '\0';
  EXPECT_EQ(StringRef(StrBuffer), "\ts_version UC_VERSION_GFX10");

  // Test that second disassembler in same context works as expected.
  AnnoStr.clear();
  InsnStr.clear();
  std::unique_ptr<MCDisassembler> DisAsm2(
      TheTarget->createMCDisassembler(*STI, *Ctx));
  Status = DisAsm2->getInstruction(Inst2, InstSize, Bytes, 0, Annotations);
  ASSERT_TRUE(Status == MCDisassembler::Success);
  EXPECT_EQ(InstSize, 4U);

  IP->printInst(&Inst2, 0U, Annotations.str(), *STI, FormattedOS);
  ASSERT_TRUE(InsnStr.size() < (sizeof(StrBuffer) - 1));
  std::memcpy(StrBuffer, InsnStr.data(), InsnStr.size());
  StrBuffer[InsnStr.size()] = '\0';
  EXPECT_EQ(StringRef(StrBuffer), "\ts_version UC_VERSION_GFX10");
}

// Test UC_VERSION symbols can be overriden without crashing.
// There is no valid behaviour if symbols are redefined in this way.
TEST(AMDGPUDisassembler, UCVersionOverride) {
  LLVMInitializeAMDGPUTargetInfo();
  LLVMInitializeAMDGPUTargetMC();
  LLVMInitializeAMDGPUDisassembler();

  std::string Error;
  const Target *TheTarget = TargetRegistry::lookupTarget(TripleName, Error);

  // Skip test if AMDGPU not built.
  if (!TheTarget)
    GTEST_SKIP();

  std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(TripleName));
  std::unique_ptr<MCAsmInfo> MAI(
      TheTarget->createMCAsmInfo(*MRI, TripleName, MCTargetOptions()));
  std::unique_ptr<const MCInstrInfo> MII(TheTarget->createMCInstrInfo());
  std::unique_ptr<MCSubtargetInfo> STI(
      TheTarget->createMCSubtargetInfo(TripleName, CPUName, ""));
  auto Ctx = std::make_unique<MCContext>(Triple(TripleName), MAI.get(),
                                         MRI.get(), STI.get());

  // Define custom UC_VERSION before initializing disassembler.
  const uint8_t UC_VERSION_GFX10_DEFAULT = 0x04;
  const uint8_t UC_VERSION_GFX10_NEW = 0x99;
  auto Sym = Ctx->getOrCreateSymbol("UC_VERSION_GFX10");
  Sym->setVariableValue(MCConstantExpr::create(UC_VERSION_GFX10_NEW, *Ctx));

  int AsmPrinterVariant = MAI->getAssemblerDialect();
  std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter(
      Triple(TripleName), AsmPrinterVariant, *MAI, *MII, *MRI));

  testing::internal::CaptureStderr();
  std::unique_ptr<MCDisassembler> DisAsm(
      TheTarget->createMCDisassembler(*STI, *Ctx));
  std::string Output = testing::internal::GetCapturedStderr();
  EXPECT_TRUE(Output.find("<unknown>:0: warning: unsupported redefinition of "
                          "UC_VERSION_GFX10") != std::string::npos);

  SmallVector<char, 64> InsnStr, AnnoStr;
  raw_svector_ostream OS(InsnStr);
  raw_svector_ostream Annotations(AnnoStr);
  formatted_raw_ostream FormattedOS(OS);

  char StrBuffer[128];

  // Decode S_VERSION instruction with original or custom version.
  uint8_t Versions[] = {UC_VERSION_GFX10_DEFAULT, UC_VERSION_GFX10_NEW};
  for (uint8_t Version : Versions) {
    uint8_t Bytes[] = {Version, 0x00, 0x80, 0xb0};
    uint64_t InstSize = 0U;
    MCInst Inst;

    AnnoStr.clear();
    InsnStr.clear();
    MCDisassembler::DecodeStatus Status =
        DisAsm->getInstruction(Inst, InstSize, Bytes, 0, Annotations);
    ASSERT_TRUE(Status == MCDisassembler::Success);
    EXPECT_EQ(InstSize, 4U);

    IP->printInst(&Inst, 0, Annotations.str(), *STI, FormattedOS);
    ASSERT_TRUE(InsnStr.size() < (sizeof(StrBuffer) - 1));
    std::memcpy(StrBuffer, InsnStr.data(), InsnStr.size());
    StrBuffer[InsnStr.size()] = '\0';

    if (Version == UC_VERSION_GFX10_DEFAULT)
      EXPECT_EQ(StringRef(StrBuffer), "\ts_version UC_VERSION_GFX10");
    else
      EXPECT_EQ(StringRef(StrBuffer), "\ts_version 153");
  }
}
