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

#include "llvm/Support/MemoryBuffer.h"

#include "../common/ThreadPostMortemTrace.h"
#include "DecodedThread.h"
#include "TraceIntelPT.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/Section.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/StringExtractor.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::trace_intel_pt;
using namespace llvm;

/// Move the decoder forward to the next synchronization point (i.e. next PSB
/// packet).
///
/// Once the decoder is at that sync. point, it can start decoding instructions.
///
/// \return
///   A negative number with the libipt error if we couldn't synchronize.
///   Otherwise, a positive number with the synchronization status will be
///   returned.
static int FindNextSynchronizationPoint(pt_insn_decoder &decoder) {
  // Try to sync the decoder. If it fails, then get
  // the decoder_offset and try to sync again from
  // the next synchronization point. If the
  // new_decoder_offset is same as decoder_offset
  // then we can't move to the next synchronization
  // point. Otherwise, keep resyncing until either
  // end of trace stream (eos) is reached or
  // pt_insn_sync_forward() passes.
  int errcode = pt_insn_sync_forward(&decoder);

  if (errcode != -pte_eos && errcode < 0) {
    uint64_t decoder_offset = 0;
    int errcode_off = pt_insn_get_offset(&decoder, &decoder_offset);
    if (errcode_off >= 0) { // we could get the offset
      while (true) {
        errcode = pt_insn_sync_forward(&decoder);
        if (errcode >= 0 || errcode == -pte_eos)
          break;

        uint64_t new_decoder_offset = 0;
        errcode_off = pt_insn_get_offset(&decoder, &new_decoder_offset);
        if (errcode_off < 0)
          break; // We can't further synchronize.
        else if (new_decoder_offset <= decoder_offset) {
          // We tried resyncing the decoder and
          // decoder didn't make any progress because
          // the offset didn't change. We will not
          // make any progress further. Hence,
          // stopping in this situation.
          break;
        }
        // We'll try again starting from a new offset.
        decoder_offset = new_decoder_offset;
      }
    }
  }

  return errcode;
}

/// Before querying instructions, we need to query the events associated that
/// instruction e.g. timing events like ptev_tick, or paging events like
/// ptev_paging.
///
/// \return
///   0 if there were no errors processing the events, or a negative libipt
///   error code in case of errors.
static int ProcessPTEvents(pt_insn_decoder &decoder, int errcode) {
  while (errcode & pts_event_pending) {
    pt_event event;
    errcode = pt_insn_event(&decoder, &event, sizeof(event));
    if (errcode < 0)
      return errcode;
  }
  return 0;
}

/// Decode all the instructions from a configured decoder.
/// The decoding flow is based on
/// https://github.com/intel/libipt/blob/master/doc/howto_libipt.md#the-instruction-flow-decode-loop
/// but with some relaxation to allow for gaps in the trace.
///
/// Error codes returned by libipt while decoding are:
/// - negative: actual errors
/// - positive or zero: not an error, but a list of bits signaling the status of
/// the decoder
///
/// \param[in] decoder
///   A configured libipt \a pt_insn_decoder.
///
/// \return
///   The decoded instructions.
static std::vector<IntelPTInstruction>
DecodeInstructions(pt_insn_decoder &decoder) {
  std::vector<IntelPTInstruction> instructions;

  while (true) {
    int errcode = FindNextSynchronizationPoint(decoder);
    if (errcode == -pte_eos)
      break;

    if (errcode < 0) {
      instructions.emplace_back(make_error<IntelPTError>(errcode));
      break;
    }

    // We have synchronized, so we can start decoding
    // instructions and events.
    while (true) {
      errcode = ProcessPTEvents(decoder, errcode);
      if (errcode < 0) {
        instructions.emplace_back(make_error<IntelPTError>(errcode));
        break;
      }
      pt_insn insn;

      errcode = pt_insn_next(&decoder, &insn, sizeof(insn));
      if (errcode == -pte_eos)
        break;

      if (errcode < 0) {
        instructions.emplace_back(make_error<IntelPTError>(errcode, insn.ip));
        break;
      }

      uint64_t time;
      int time_error = pt_insn_time(&decoder, &time, nullptr, nullptr);
      if (time_error == -pte_invalid) {
        // This happens if we invoke the pt_insn_time method incorrectly,
        // but the instruction is good though.
        instructions.emplace_back(
            make_error<IntelPTError>(time_error, insn.ip));
        instructions.emplace_back(insn);
        break;
      }
      if (time_error == -pte_no_time) {
        // We simply don't have time information, i.e. None of TSC, MTC or CYC
        // was enabled.
        instructions.emplace_back(insn);
      } else {
        instructions.emplace_back(insn, time);
      }
    }
  }

  return instructions;
}

/// Callback used by libipt for reading the process memory.
///
/// More information can be found in
/// https://github.com/intel/libipt/blob/master/doc/man/pt_image_set_callback.3.md
static int ReadProcessMemory(uint8_t *buffer, size_t size,
                             const pt_asid * /* unused */, uint64_t pc,
                             void *context) {
  Process *process = static_cast<Process *>(context);

  Status error;
  int bytes_read = process->ReadMemory(pc, buffer, size, error);
  if (error.Fail())
    return -pte_nomap;
  return bytes_read;
}

static Expected<std::vector<IntelPTInstruction>>
DecodeInMemoryTrace(Process &process, TraceIntelPT &trace_intel_pt,
                    MutableArrayRef<uint8_t> buffer) {
  Expected<pt_cpu> cpu_info = trace_intel_pt.GetCPUInfo();
  if (!cpu_info)
    return cpu_info.takeError();

  pt_config config;
  pt_config_init(&config);
  config.cpu = *cpu_info;

  if (int errcode = pt_cpu_errata(&config.errata, &config.cpu))
    return make_error<IntelPTError>(errcode);

  config.begin = buffer.data();
  config.end = buffer.data() + buffer.size();

  pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config);
  if (!decoder)
    return make_error<IntelPTError>(-pte_nomem);

  pt_image *image = pt_insn_get_image(decoder);

  int errcode = pt_image_set_callback(image, ReadProcessMemory, &process);
  assert(errcode == 0);
  (void)errcode;

  std::vector<IntelPTInstruction> instructions = DecodeInstructions(*decoder);

  pt_insn_free_decoder(decoder);
  return instructions;
}

static Expected<std::vector<IntelPTInstruction>>
DecodeTraceFile(Process &process, TraceIntelPT &trace_intel_pt,
                const FileSpec &trace_file, size_t &raw_trace_size) {
  ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error =
      MemoryBuffer::getFile(trace_file.GetPath());
  if (std::error_code err = trace_or_error.getError())
    return errorCodeToError(err);

  MemoryBuffer &trace = **trace_or_error;
  MutableArrayRef<uint8_t> trace_data(
      // The libipt library does not modify the trace buffer, hence the
      // following cast is safe.
      reinterpret_cast<uint8_t *>(const_cast<char *>(trace.getBufferStart())),
      trace.getBufferSize());
  raw_trace_size = trace_data.size();
  return DecodeInMemoryTrace(process, trace_intel_pt, trace_data);
}

static Expected<std::vector<IntelPTInstruction>>
DecodeLiveThread(Thread &thread, TraceIntelPT &trace, size_t &raw_trace_size) {
  Expected<std::vector<uint8_t>> buffer =
      trace.GetLiveThreadBuffer(thread.GetID());
  if (!buffer)
    return buffer.takeError();
  raw_trace_size = buffer->size();
  if (Expected<pt_cpu> cpu_info = trace.GetCPUInfo())
    return DecodeInMemoryTrace(*thread.GetProcess(), trace,
                               MutableArrayRef<uint8_t>(*buffer));
  else
    return cpu_info.takeError();
}

DecodedThreadSP ThreadDecoder::Decode() {
  if (!m_decoded_thread.hasValue())
    m_decoded_thread = DoDecode();
  return *m_decoded_thread;
}

PostMortemThreadDecoder::PostMortemThreadDecoder(
    const lldb::ThreadPostMortemTraceSP &trace_thread, TraceIntelPT &trace)
    : m_trace_thread(trace_thread), m_trace(trace) {}

DecodedThreadSP PostMortemThreadDecoder::DoDecode() {
  size_t raw_trace_size = 0;
  if (Expected<std::vector<IntelPTInstruction>> instructions =
          DecodeTraceFile(*m_trace_thread->GetProcess(), m_trace,
                          m_trace_thread->GetTraceFile(), raw_trace_size))
    return std::make_shared<DecodedThread>(m_trace_thread->shared_from_this(),
                                           std::move(*instructions),
                                           raw_trace_size);
  else
    return std::make_shared<DecodedThread>(m_trace_thread->shared_from_this(),
                                           instructions.takeError());
}

LiveThreadDecoder::LiveThreadDecoder(Thread &thread, TraceIntelPT &trace)
    : m_thread_sp(thread.shared_from_this()), m_trace(trace) {}

DecodedThreadSP LiveThreadDecoder::DoDecode() {
  size_t raw_trace_size = 0;
  if (Expected<std::vector<IntelPTInstruction>> instructions =
          DecodeLiveThread(*m_thread_sp, m_trace, raw_trace_size))
    return std::make_shared<DecodedThread>(
        m_thread_sp, std::move(*instructions), raw_trace_size);
  else
    return std::make_shared<DecodedThread>(m_thread_sp,
                                           instructions.takeError());
}
