//===-- ArchitectureMips.cpp -------------------------------------*- 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
//
//===----------------------------------------------------------------------===//

#include "Plugins/Architecture/Mips/ArchitectureMips.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/Disassembler.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/Log.h"

using namespace lldb_private;
using namespace lldb;

ConstString ArchitectureMips::GetPluginNameStatic() {
  return ConstString("mips");
}

void ArchitectureMips::Initialize() {
  PluginManager::RegisterPlugin(GetPluginNameStatic(),
                                "Mips-specific algorithms",
                                &ArchitectureMips::Create);
}

void ArchitectureMips::Terminate() {
  PluginManager::UnregisterPlugin(&ArchitectureMips::Create);
}

std::unique_ptr<Architecture> ArchitectureMips::Create(const ArchSpec &arch) {
  return arch.IsMIPS() ?
      std::unique_ptr<Architecture>(new ArchitectureMips(arch)) : nullptr;
}

ConstString ArchitectureMips::GetPluginName() { return GetPluginNameStatic(); }
uint32_t ArchitectureMips::GetPluginVersion() { return 1; }

addr_t ArchitectureMips::GetCallableLoadAddress(addr_t code_addr,
                                                AddressClass addr_class) const {
  bool is_alternate_isa = false;

  switch (addr_class) {
  case AddressClass::eData:
  case AddressClass::eDebug:
    return LLDB_INVALID_ADDRESS;
  case AddressClass::eCodeAlternateISA:
    is_alternate_isa = true;
    break;
  default: break;
  }

  if ((code_addr & 2ull) || is_alternate_isa)
    return code_addr | 1u;
  return code_addr;
}

addr_t ArchitectureMips::GetOpcodeLoadAddress(addr_t opcode_addr,
                                              AddressClass addr_class) const {
  switch (addr_class) {
  case AddressClass::eData:
  case AddressClass::eDebug:
    return LLDB_INVALID_ADDRESS;
  default: break;
  }
  return opcode_addr & ~(1ull);
}

lldb::addr_t ArchitectureMips::GetBreakableLoadAddress(lldb::addr_t addr,
                                                       Target &target) const {

  Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS));

  Address resolved_addr;

  SectionLoadList &section_load_list = target.GetSectionLoadList();
  if (section_load_list.IsEmpty())
    // No sections are loaded, so we must assume we are not running yet and
    // need to operate only on file address.
    target.ResolveFileAddress(addr, resolved_addr);
  else
    target.ResolveLoadAddress(addr, resolved_addr);

  addr_t current_offset = 0;

  // Get the function boundaries to make sure we don't scan back before the
  // beginning of the current function.
  ModuleSP temp_addr_module_sp(resolved_addr.GetModule());
  if (temp_addr_module_sp) {
    SymbolContext sc;
    SymbolContextItem resolve_scope =
        eSymbolContextFunction | eSymbolContextSymbol;
    temp_addr_module_sp->ResolveSymbolContextForAddress(resolved_addr,
      resolve_scope, sc);
    Address sym_addr;
    if (sc.function)
      sym_addr = sc.function->GetAddressRange().GetBaseAddress();
    else if (sc.symbol)
      sym_addr = sc.symbol->GetAddress();

    addr_t function_start = sym_addr.GetLoadAddress(&target);
    if (function_start == LLDB_INVALID_ADDRESS)
      function_start = sym_addr.GetFileAddress();

    if (function_start)
      current_offset = addr - function_start;
  }

  // If breakpoint address is start of function then we dont have to do
  // anything.
  if (current_offset == 0)
    return addr;

  ExecutionContext ctx;
  target.CalculateExecutionContext(ctx);
  auto insn = GetInstructionAtAddress(ctx, current_offset, addr);

  if (nullptr == insn || !insn->HasDelaySlot())
    return addr;

  // Adjust the breakable address
  uint64_t breakable_addr = addr - insn->GetOpcode().GetByteSize();
  LLDB_LOGF(log,
            "Target::%s Breakpoint at 0x%8.8" PRIx64
            " is adjusted to 0x%8.8" PRIx64 " due to delay slot\n",
            __FUNCTION__, addr, breakable_addr);

  return breakable_addr;
}

Instruction *ArchitectureMips::GetInstructionAtAddress(
    const ExecutionContext &exe_ctx, const Address &resolved_addr,
    addr_t symbol_offset) const {

  auto loop_count = symbol_offset / 2;

  uint32_t arch_flags = m_arch.GetFlags();
  bool IsMips16 = arch_flags & ArchSpec::eMIPSAse_mips16;
  bool IsMicromips = arch_flags & ArchSpec::eMIPSAse_micromips;

  if (loop_count > 3) {
    // Scan previous 6 bytes
    if (IsMips16 | IsMicromips)
      loop_count = 3;
    // For mips-only, instructions are always 4 bytes, so scan previous 4
    // bytes only.
    else
      loop_count = 2;
  }

  // Create Disassembler Instance
  lldb::DisassemblerSP disasm_sp(
    Disassembler::FindPlugin(m_arch, nullptr, nullptr));

  InstructionList instruction_list;
  InstructionSP prev_insn;
  bool prefer_file_cache = true; // Read from file
  uint32_t inst_to_choose = 0;

  Address addr = resolved_addr;

  for (uint32_t i = 1; i <= loop_count; i++) {
    // Adjust the address to read from.
    addr.Slide(-2);
    AddressRange range(addr, i * 2);
    uint32_t insn_size = 0;

    disasm_sp->ParseInstructions(&exe_ctx, range, nullptr, prefer_file_cache);

    uint32_t num_insns = disasm_sp->GetInstructionList().GetSize();
    if (num_insns) {
      prev_insn = disasm_sp->GetInstructionList().GetInstructionAtIndex(0);
      insn_size = prev_insn->GetOpcode().GetByteSize();
      if (i == 1 && insn_size == 2) {
        // This looks like a valid 2-byte instruction (but it could be a part
        // of upper 4 byte instruction).
        instruction_list.Append(prev_insn);
        inst_to_choose = 1;
      }
      else if (i == 2) {
        // Here we may get one 4-byte instruction or two 2-byte instructions.
        if (num_insns == 2) {
          // Looks like there are two 2-byte instructions above our
          // breakpoint target address. Now the upper 2-byte instruction is
          // either a valid 2-byte instruction or could be a part of it's
          // upper 4-byte instruction. In both cases we don't care because in
          // this case lower 2-byte instruction is definitely a valid
          // instruction and whatever i=1 iteration has found out is true.
          inst_to_choose = 1;
          break;
        }
        else if (insn_size == 4) {
          // This instruction claims its a valid 4-byte instruction. But it
          // could be a part of it's upper 4-byte instruction. Lets try
          // scanning upper 2 bytes to verify this.
          instruction_list.Append(prev_insn);
          inst_to_choose = 2;
        }
      }
      else if (i == 3) {
        if (insn_size == 4)
          // FIXME: We reached here that means instruction at [target - 4] has
          // already claimed to be a 4-byte instruction, and now instruction
          // at [target - 6] is also claiming that it's a 4-byte instruction.
          // This can not be true. In this case we can not decide the valid
          // previous instruction so we let lldb set the breakpoint at the
          // address given by user.
          inst_to_choose = 0;
        else
          // This is straight-forward
          inst_to_choose = 2;
        break;
      }
    }
    else {
      // Decode failed, bytes do not form a valid instruction. So whatever
      // previous iteration has found out is true.
      if (i > 1) {
        inst_to_choose = i - 1;
        break;
      }
    }
  }

  // Check if we are able to find any valid instruction.
  if (inst_to_choose) {
    if (inst_to_choose > instruction_list.GetSize())
      inst_to_choose--;
    return instruction_list.GetInstructionAtIndex(inst_to_choose - 1).get();
  }

  return nullptr;
}
