//===-- RegisterContextMinidump_ARM64.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 "RegisterContextMinidump_ARM64.h"

#include "Utility/ARM64_DWARF_Registers.h"
#include "lldb/Utility/RegisterValue.h"
#include "lldb/Utility/DataExtractor.h"
#include "lldb/lldb-enumerations.h"

// C includes
#include <cassert>

// C++ includes

using namespace lldb;
using namespace lldb_private;
using namespace minidump;

#define INV LLDB_INVALID_REGNUM
#define OFFSET(r) (offsetof(RegisterContextMinidump_ARM64::Context, r))

#define DEF_X(i)                                                               \
  {                                                                            \
    "x" #i, nullptr, 8, OFFSET(x) + i * 8, eEncodingUint, eFormatHex,          \
        {arm64_dwarf::x##i, arm64_dwarf::x##i, INV, INV, reg_x##i},            \
        nullptr, nullptr, nullptr,                                             \
  }

#define DEF_W(i)                                                               \
  {                                                                            \
    "w" #i, nullptr, 4, OFFSET(x) + i * 8, eEncodingUint, eFormatHex,          \
        {INV, INV, INV, INV, reg_w##i}, nullptr, nullptr, nullptr,             \
  }

#define DEF_X_ARG(i, n)                                                        \
  {                                                                            \
    "x" #i, "arg" #n, 8, OFFSET(x) + i * 8, eEncodingUint, eFormatHex,         \
        {arm64_dwarf::x##i, arm64_dwarf::x##i, LLDB_REGNUM_GENERIC_ARG1 + i,   \
         INV, reg_x##i}, nullptr, nullptr, nullptr,                            \
  }

#define DEF_V(i)                                                               \
  {                                                                            \
    "v" #i, nullptr, 16, OFFSET(v) + i * 16, eEncodingVector,                  \
        eFormatVectorOfUInt8, {arm64_dwarf::v##i, arm64_dwarf::v##i, INV, INV, \
        reg_v##i}, nullptr, nullptr, nullptr,                                  \
  }

#define DEF_D(i)                                                               \
  {                                                                            \
    "d" #i, nullptr, 8, OFFSET(v) + i * 16, eEncodingVector,                   \
        eFormatVectorOfUInt8, {INV, INV, INV, INV, reg_d##i}, nullptr,         \
        nullptr, nullptr,                                                      \
  }

#define DEF_S(i)                                                               \
  {                                                                            \
    "s" #i, nullptr, 4, OFFSET(v) + i * 16, eEncodingVector,                   \
        eFormatVectorOfUInt8, {INV, INV, INV, INV, reg_s##i}, nullptr,         \
        nullptr, nullptr,                                                      \
  }

#define DEF_H(i)                                                               \
  {                                                                            \
    "h" #i, nullptr, 2, OFFSET(v) + i * 16, eEncodingVector,                   \
        eFormatVectorOfUInt8, {INV, INV, INV, INV, reg_h##i}, nullptr,         \
        nullptr, nullptr,                                                      \
  }

// Zero based LLDB register numbers for this register context
enum {
  // General Purpose Registers
  reg_x0 = 0,
  reg_x1,
  reg_x2,
  reg_x3,
  reg_x4,
  reg_x5,
  reg_x6,
  reg_x7,
  reg_x8,
  reg_x9,
  reg_x10,
  reg_x11,
  reg_x12,
  reg_x13,
  reg_x14,
  reg_x15,
  reg_x16,
  reg_x17,
  reg_x18,
  reg_x19,
  reg_x20,
  reg_x21,
  reg_x22,
  reg_x23,
  reg_x24,
  reg_x25,
  reg_x26,
  reg_x27,
  reg_x28,
  reg_fp,
  reg_lr,
  reg_sp,
  reg_pc,
  reg_w0,
  reg_w1,
  reg_w2,
  reg_w3,
  reg_w4,
  reg_w5,
  reg_w6,
  reg_w7,
  reg_w8,
  reg_w9,
  reg_w10,
  reg_w11,
  reg_w12,
  reg_w13,
  reg_w14,
  reg_w15,
  reg_w16,
  reg_w17,
  reg_w18,
  reg_w19,
  reg_w20,
  reg_w21,
  reg_w22,
  reg_w23,
  reg_w24,
  reg_w25,
  reg_w26,
  reg_w27,
  reg_w28,
  reg_w29,
  reg_w30,
  reg_w31,
  reg_cpsr,
  // Floating Point Registers
  reg_fpsr,
  reg_fpcr,
  reg_v0,
  reg_v1,
  reg_v2,
  reg_v3,
  reg_v4,
  reg_v5,
  reg_v6,
  reg_v7,
  reg_v8,
  reg_v9,
  reg_v10,
  reg_v11,
  reg_v12,
  reg_v13,
  reg_v14,
  reg_v15,
  reg_v16,
  reg_v17,
  reg_v18,
  reg_v19,
  reg_v20,
  reg_v21,
  reg_v22,
  reg_v23,
  reg_v24,
  reg_v25,
  reg_v26,
  reg_v27,
  reg_v28,
  reg_v29,
  reg_v30,
  reg_v31,
  reg_d0,
  reg_d1,
  reg_d2,
  reg_d3,
  reg_d4,
  reg_d5,
  reg_d6,
  reg_d7,
  reg_d8,
  reg_d9,
  reg_d10,
  reg_d11,
  reg_d12,
  reg_d13,
  reg_d14,
  reg_d15,
  reg_d16,
  reg_d17,
  reg_d18,
  reg_d19,
  reg_d20,
  reg_d21,
  reg_d22,
  reg_d23,
  reg_d24,
  reg_d25,
  reg_d26,
  reg_d27,
  reg_d28,
  reg_d29,
  reg_d30,
  reg_d31,
  reg_s0,
  reg_s1,
  reg_s2,
  reg_s3,
  reg_s4,
  reg_s5,
  reg_s6,
  reg_s7,
  reg_s8,
  reg_s9,
  reg_s10,
  reg_s11,
  reg_s12,
  reg_s13,
  reg_s14,
  reg_s15,
  reg_s16,
  reg_s17,
  reg_s18,
  reg_s19,
  reg_s20,
  reg_s21,
  reg_s22,
  reg_s23,
  reg_s24,
  reg_s25,
  reg_s26,
  reg_s27,
  reg_s28,
  reg_s29,
  reg_s30,
  reg_s31,
  reg_h0,
  reg_h1,
  reg_h2,
  reg_h3,
  reg_h4,
  reg_h5,
  reg_h6,
  reg_h7,
  reg_h8,
  reg_h9,
  reg_h10,
  reg_h11,
  reg_h12,
  reg_h13,
  reg_h14,
  reg_h15,
  reg_h16,
  reg_h17,
  reg_h18,
  reg_h19,
  reg_h20,
  reg_h21,
  reg_h22,
  reg_h23,
  reg_h24,
  reg_h25,
  reg_h26,
  reg_h27,
  reg_h28,
  reg_h29,
  reg_h30,
  reg_h31,
  k_num_regs
};

// Register info definitions for this register context
static RegisterInfo g_reg_infos[] = {
    DEF_X_ARG(0, 1),
    DEF_X_ARG(1, 2),
    DEF_X_ARG(2, 3),
    DEF_X_ARG(3, 4),
    DEF_X_ARG(4, 5),
    DEF_X_ARG(5, 6),
    DEF_X_ARG(6, 7),
    DEF_X_ARG(7, 8),
    DEF_X(8),
    DEF_X(9),
    DEF_X(10),
    DEF_X(11),
    DEF_X(12),
    DEF_X(13),
    DEF_X(14),
    DEF_X(15),
    DEF_X(16),
    DEF_X(17),
    DEF_X(18),
    DEF_X(19),
    DEF_X(20),
    DEF_X(21),
    DEF_X(22),
    DEF_X(23),
    DEF_X(24),
    DEF_X(25),
    DEF_X(26),
    DEF_X(27),
    DEF_X(28),
    {"fp",
     "x29",
     8,
     OFFSET(x) + 29 * 8,
     eEncodingUint,
     eFormatHex,
     {arm64_dwarf::x29, arm64_dwarf::x29, LLDB_REGNUM_GENERIC_FP, INV, reg_fp},
     nullptr,
     nullptr,
     nullptr,
    },
    {"lr",
     "x30",
     8,
     OFFSET(x) + 30 * 8,
     eEncodingUint,
     eFormatHex,
     {arm64_dwarf::x30, arm64_dwarf::x30, LLDB_REGNUM_GENERIC_RA, INV, reg_lr},
     nullptr,
     nullptr,
     nullptr,
    },
    {"sp",
     "x31",
     8,
     OFFSET(x) + 31 * 8,
     eEncodingUint,
     eFormatHex,
     {arm64_dwarf::x31, arm64_dwarf::x31, LLDB_REGNUM_GENERIC_SP, INV, reg_sp},
     nullptr,
     nullptr,
     nullptr,
    },
    {"pc",
     nullptr,
     8,
     OFFSET(pc),
     eEncodingUint,
     eFormatHex,
     {arm64_dwarf::pc, arm64_dwarf::pc, LLDB_REGNUM_GENERIC_PC, INV, reg_pc},
     nullptr,
     nullptr,
     nullptr,
    },
    // w0 - w31
    DEF_W(0),
    DEF_W(1),
    DEF_W(2),
    DEF_W(3),
    DEF_W(4),
    DEF_W(5),
    DEF_W(6),
    DEF_W(7),
    DEF_W(8),
    DEF_W(9),
    DEF_W(10),
    DEF_W(11),
    DEF_W(12),
    DEF_W(13),
    DEF_W(14),
    DEF_W(15),
    DEF_W(16),
    DEF_W(17),
    DEF_W(18),
    DEF_W(19),
    DEF_W(20),
    DEF_W(21),
    DEF_W(22),
    DEF_W(23),
    DEF_W(24),
    DEF_W(25),
    DEF_W(26),
    DEF_W(27),
    DEF_W(28),
    DEF_W(29),
    DEF_W(30),
    DEF_W(31),
    {"cpsr",
     "psr",
     4,
     OFFSET(cpsr),
     eEncodingUint,
     eFormatHex,
     {INV, arm64_dwarf::cpsr, LLDB_REGNUM_GENERIC_FLAGS, INV, reg_cpsr},
     nullptr,
     nullptr,
     nullptr,
    },
    {"fpsr",
     nullptr,
     4,
     OFFSET(fpsr),
     eEncodingUint,
     eFormatHex,
     {INV, INV, INV, INV, reg_fpsr},
     nullptr,
     nullptr,
     nullptr,
    },
    {"fpcr",
     nullptr,
     4,
     OFFSET(fpcr),
     eEncodingUint,
     eFormatHex,
     {INV, INV, INV, INV, reg_fpcr},
     nullptr,
     nullptr,
     nullptr,
    },
    // v0 - v31
    DEF_V(0),
    DEF_V(1),
    DEF_V(2),
    DEF_V(3),
    DEF_V(4),
    DEF_V(5),
    DEF_V(6),
    DEF_V(7),
    DEF_V(8),
    DEF_V(9),
    DEF_V(10),
    DEF_V(11),
    DEF_V(12),
    DEF_V(13),
    DEF_V(14),
    DEF_V(15),
    DEF_V(16),
    DEF_V(17),
    DEF_V(18),
    DEF_V(19),
    DEF_V(20),
    DEF_V(21),
    DEF_V(22),
    DEF_V(23),
    DEF_V(24),
    DEF_V(25),
    DEF_V(26),
    DEF_V(27),
    DEF_V(28),
    DEF_V(29),
    DEF_V(30),
    DEF_V(31),
    // d0 - d31
    DEF_D(0),
    DEF_D(1),
    DEF_D(2),
    DEF_D(3),
    DEF_D(4),
    DEF_D(5),
    DEF_D(6),
    DEF_D(7),
    DEF_D(8),
    DEF_D(9),
    DEF_D(10),
    DEF_D(11),
    DEF_D(12),
    DEF_D(13),
    DEF_D(14),
    DEF_D(15),
    DEF_D(16),
    DEF_D(17),
    DEF_D(18),
    DEF_D(19),
    DEF_D(20),
    DEF_D(21),
    DEF_D(22),
    DEF_D(23),
    DEF_D(24),
    DEF_D(25),
    DEF_D(26),
    DEF_D(27),
    DEF_D(28),
    DEF_D(29),
    DEF_D(30),
    DEF_D(31),
    // s0 - s31
    DEF_S(0),
    DEF_S(1),
    DEF_S(2),
    DEF_S(3),
    DEF_S(4),
    DEF_S(5),
    DEF_S(6),
    DEF_S(7),
    DEF_S(8),
    DEF_S(9),
    DEF_S(10),
    DEF_S(11),
    DEF_S(12),
    DEF_S(13),
    DEF_S(14),
    DEF_S(15),
    DEF_S(16),
    DEF_S(17),
    DEF_S(18),
    DEF_S(19),
    DEF_S(20),
    DEF_S(21),
    DEF_S(22),
    DEF_S(23),
    DEF_S(24),
    DEF_S(25),
    DEF_S(26),
    DEF_S(27),
    DEF_S(28),
    DEF_S(29),
    DEF_S(30),
    DEF_S(31),
    // h0 - h31
    DEF_H(0),
    DEF_H(1),
    DEF_H(2),
    DEF_H(3),
    DEF_H(4),
    DEF_H(5),
    DEF_H(6),
    DEF_H(7),
    DEF_H(8),
    DEF_H(9),
    DEF_H(10),
    DEF_H(11),
    DEF_H(12),
    DEF_H(13),
    DEF_H(14),
    DEF_H(15),
    DEF_H(16),
    DEF_H(17),
    DEF_H(18),
    DEF_H(19),
    DEF_H(20),
    DEF_H(21),
    DEF_H(22),
    DEF_H(23),
    DEF_H(24),
    DEF_H(25),
    DEF_H(26),
    DEF_H(27),
    DEF_H(28),
    DEF_H(29),
    DEF_H(30),
    DEF_H(31),
};

constexpr size_t k_num_reg_infos = std::size(g_reg_infos);

// ARM64 general purpose registers.
const uint32_t g_gpr_regnums[] = {
    reg_x0,
    reg_x1,
    reg_x2,
    reg_x3,
    reg_x4,
    reg_x5,
    reg_x6,
    reg_x7,
    reg_x8,
    reg_x9,
    reg_x10,
    reg_x11,
    reg_x12,
    reg_x13,
    reg_x14,
    reg_x15,
    reg_x16,
    reg_x17,
    reg_x18,
    reg_x19,
    reg_x20,
    reg_x21,
    reg_x22,
    reg_x23,
    reg_x24,
    reg_x25,
    reg_x26,
    reg_x27,
    reg_x28,
    reg_fp,
    reg_lr,
    reg_sp,
    reg_w0,
    reg_w1,
    reg_w2,
    reg_w3,
    reg_w4,
    reg_w5,
    reg_w6,
    reg_w7,
    reg_w8,
    reg_w9,
    reg_w10,
    reg_w11,
    reg_w12,
    reg_w13,
    reg_w14,
    reg_w15,
    reg_w16,
    reg_w17,
    reg_w18,
    reg_w19,
    reg_w20,
    reg_w21,
    reg_w22,
    reg_w23,
    reg_w24,
    reg_w25,
    reg_w26,
    reg_w27,
    reg_w28,
    reg_w29,
    reg_w30,
    reg_w31,
    reg_pc,
    reg_cpsr,
    LLDB_INVALID_REGNUM // register sets need to end with this flag
};
const uint32_t g_fpu_regnums[] = {
    reg_v0,
    reg_v1,
    reg_v2,
    reg_v3,
    reg_v4,
    reg_v5,
    reg_v6,
    reg_v7,
    reg_v8,
    reg_v9,
    reg_v10,
    reg_v11,
    reg_v12,
    reg_v13,
    reg_v14,
    reg_v15,
    reg_v16,
    reg_v17,
    reg_v18,
    reg_v19,
    reg_v20,
    reg_v21,
    reg_v22,
    reg_v23,
    reg_v24,
    reg_v25,
    reg_v26,
    reg_v27,
    reg_v28,
    reg_v29,
    reg_v30,
    reg_v31,
    reg_d0,
    reg_d1,
    reg_d2,
    reg_d3,
    reg_d4,
    reg_d5,
    reg_d6,
    reg_d7,
    reg_d8,
    reg_d9,
    reg_d10,
    reg_d11,
    reg_d12,
    reg_d13,
    reg_d14,
    reg_d15,
    reg_d16,
    reg_d17,
    reg_d18,
    reg_d19,
    reg_d20,
    reg_d21,
    reg_d22,
    reg_d23,
    reg_d24,
    reg_d25,
    reg_d26,
    reg_d27,
    reg_d28,
    reg_d29,
    reg_d30,
    reg_d31,
    reg_s0,
    reg_s1,
    reg_s2,
    reg_s3,
    reg_s4,
    reg_s5,
    reg_s6,
    reg_s7,
    reg_s8,
    reg_s9,
    reg_s10,
    reg_s11,
    reg_s12,
    reg_s13,
    reg_s14,
    reg_s15,
    reg_s16,
    reg_s17,
    reg_s18,
    reg_s19,
    reg_s20,
    reg_s21,
    reg_s22,
    reg_s23,
    reg_s24,
    reg_s25,
    reg_s26,
    reg_s27,
    reg_s28,
    reg_s29,
    reg_s30,
    reg_s31,
    reg_h0,
    reg_h1,
    reg_h2,
    reg_h3,
    reg_h4,
    reg_h5,
    reg_h6,
    reg_h7,
    reg_h8,
    reg_h9,
    reg_h10,
    reg_h11,
    reg_h12,
    reg_h13,
    reg_h14,
    reg_h15,
    reg_h16,
    reg_h17,
    reg_h18,
    reg_h19,
    reg_h20,
    reg_h21,
    reg_h22,
    reg_h23,
    reg_h24,
    reg_h25,
    reg_h26,
    reg_h27,
    reg_h28,
    reg_h29,
    reg_h30,
    reg_h31,
    reg_fpsr,
    reg_fpcr,
    LLDB_INVALID_REGNUM // register sets need to end with this flag
};

// Skip the last LLDB_INVALID_REGNUM in each count below by subtracting 1
constexpr size_t k_num_gpr_regs = std::size(g_gpr_regnums) - 1;
constexpr size_t k_num_fpu_regs = std::size(g_fpu_regnums) - 1;

static RegisterSet g_reg_sets[] = {
    {"General Purpose Registers", "gpr", k_num_gpr_regs, g_gpr_regnums},
    {"Floating Point Registers", "fpu", k_num_fpu_regs, g_fpu_regnums},
};

constexpr size_t k_num_reg_sets = std::size(g_reg_sets);

RegisterContextMinidump_ARM64::RegisterContextMinidump_ARM64(
    lldb_private::Thread &thread, const DataExtractor &data)
    : RegisterContext(thread, 0) {
  lldb::offset_t offset = 0;
  m_regs.context_flags = data.GetU64(&offset);
  for (unsigned i = 0; i < 32; ++i)
    m_regs.x[i] = data.GetU64(&offset);
  m_regs.pc = data.GetU64(&offset);
  m_regs.cpsr = data.GetU32(&offset);
  m_regs.fpsr = data.GetU32(&offset);
  m_regs.fpcr = data.GetU32(&offset);
  auto regs_data = data.GetData(&offset, sizeof(m_regs.v));
  if (regs_data)
    memcpy(m_regs.v, regs_data, sizeof(m_regs.v));
  static_assert(k_num_regs == k_num_reg_infos);
}
size_t RegisterContextMinidump_ARM64::GetRegisterCount() { return k_num_regs; }

const RegisterInfo *
RegisterContextMinidump_ARM64::GetRegisterInfoAtIndex(size_t reg) {
  if (reg < k_num_reg_infos)
    return &g_reg_infos[reg];
  return nullptr;
}

size_t RegisterContextMinidump_ARM64::GetRegisterSetCount() {
  return k_num_reg_sets;
}

const RegisterSet *RegisterContextMinidump_ARM64::GetRegisterSet(size_t set) {
  if (set < k_num_reg_sets)
    return &g_reg_sets[set];
  return nullptr;
}

const char *RegisterContextMinidump_ARM64::GetRegisterName(unsigned reg) {
  if (reg < k_num_reg_infos)
    return g_reg_infos[reg].name;
  return nullptr;
}

bool RegisterContextMinidump_ARM64::ReadRegister(const RegisterInfo *reg_info,
                                                 RegisterValue &reg_value) {
  Status error;
  reg_value.SetFromMemoryData(
      *reg_info, (const uint8_t *)&m_regs + reg_info->byte_offset,
      reg_info->byte_size, lldb::eByteOrderLittle, error);
  return error.Success();
}

bool RegisterContextMinidump_ARM64::WriteRegister(const RegisterInfo *,
                                                  const RegisterValue &) {
  return false;
}

uint32_t RegisterContextMinidump_ARM64::ConvertRegisterKindToRegisterNumber(
    lldb::RegisterKind kind, uint32_t num) {
  for (size_t i = 0; i < k_num_regs; ++i) {
    if (g_reg_infos[i].kinds[kind] == num)
      return i;
  }
  return LLDB_INVALID_REGNUM;
}
