//===- BlockVerifier.cpp - FDR Block Verifier -----------------------------===//
//
// 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/XRay/BlockVerifier.h"
#include "llvm/Support/Error.h"

#include <bitset>

namespace llvm {
namespace xray {
namespace {

constexpr unsigned long long mask(BlockVerifier::State S) {
  return 1uLL << static_cast<std::size_t>(S);
}

constexpr std::size_t number(BlockVerifier::State S) {
  return static_cast<std::size_t>(S);
}

StringRef recordToString(BlockVerifier::State R) {
  switch (R) {
  case BlockVerifier::State::BufferExtents:
    return "BufferExtents";
  case BlockVerifier::State::NewBuffer:
    return "NewBuffer";
  case BlockVerifier::State::WallClockTime:
    return "WallClockTime";
  case BlockVerifier::State::PIDEntry:
    return "PIDEntry";
  case BlockVerifier::State::NewCPUId:
    return "NewCPUId";
  case BlockVerifier::State::TSCWrap:
    return "TSCWrap";
  case BlockVerifier::State::CustomEvent:
    return "CustomEvent";
  case BlockVerifier::State::Function:
    return "Function";
  case BlockVerifier::State::CallArg:
    return "CallArg";
  case BlockVerifier::State::EndOfBuffer:
    return "EndOfBuffer";
  case BlockVerifier::State::TypedEvent:
    return "TypedEvent";
  case BlockVerifier::State::StateMax:
  case BlockVerifier::State::Unknown:
    return "Unknown";
  }
  llvm_unreachable("Unkown state!");
}

struct Transition {
  BlockVerifier::State From;
  std::bitset<number(BlockVerifier::State::StateMax)> ToStates;
};

} // namespace

Error BlockVerifier::transition(State To) {
  using ToSet = std::bitset<number(State::StateMax)>;
  static constexpr std::array<const Transition, number(State::StateMax)>
      TransitionTable{{{State::Unknown,
                        {mask(State::BufferExtents) | mask(State::NewBuffer)}},

                       {State::BufferExtents, {mask(State::NewBuffer)}},

                       {State::NewBuffer, {mask(State::WallClockTime)}},

                       {State::WallClockTime,
                        {mask(State::PIDEntry) | mask(State::NewCPUId)}},

                       {State::PIDEntry, {mask(State::NewCPUId)}},

                       {State::NewCPUId,
                        {mask(State::NewCPUId) | mask(State::TSCWrap) |
                         mask(State::CustomEvent) | mask(State::Function) |
                         mask(State::EndOfBuffer) | mask(State::TypedEvent)}},

                       {State::TSCWrap,
                        {mask(State::TSCWrap) | mask(State::NewCPUId) |
                         mask(State::CustomEvent) | mask(State::Function) |
                         mask(State::EndOfBuffer) | mask(State::TypedEvent)}},

                       {State::CustomEvent,
                        {mask(State::CustomEvent) | mask(State::TSCWrap) |
                         mask(State::NewCPUId) | mask(State::Function) |
                         mask(State::EndOfBuffer) | mask(State::TypedEvent)}},

                       {State::TypedEvent,
                        {mask(State::TypedEvent) | mask(State::TSCWrap) |
                         mask(State::NewCPUId) | mask(State::Function) |
                         mask(State::EndOfBuffer) | mask(State::CustomEvent)}},

                       {State::Function,
                        {mask(State::Function) | mask(State::TSCWrap) |
                         mask(State::NewCPUId) | mask(State::CustomEvent) |
                         mask(State::CallArg) | mask(State::EndOfBuffer) |
                         mask(State::TypedEvent)}},

                       {State::CallArg,
                        {mask(State::CallArg) | mask(State::Function) |
                         mask(State::TSCWrap) | mask(State::NewCPUId) |
                         mask(State::CustomEvent) | mask(State::EndOfBuffer) |
                         mask(State::TypedEvent)}},

                       {State::EndOfBuffer, {}}}};

  if (CurrentRecord >= State::StateMax)
    return createStringError(
        std::make_error_code(std::errc::executable_format_error),
        "BUG (BlockVerifier): Cannot find transition table entry for %s, "
        "transitioning to %s.",
        recordToString(CurrentRecord).data(), recordToString(To).data());

  // If we're at an EndOfBuffer record, we ignore anything that follows that
  // isn't a NewBuffer record.
  if (CurrentRecord == State::EndOfBuffer && To != State::NewBuffer)
    return Error::success();

  auto &Mapping = TransitionTable[number(CurrentRecord)];
  auto &Destinations = Mapping.ToStates;
  assert(Mapping.From == CurrentRecord &&
         "BUG: Wrong index for record mapping.");
  if ((Destinations & ToSet(mask(To))) == 0)
    return createStringError(
        std::make_error_code(std::errc::executable_format_error),
        "BlockVerifier: Invalid transition from %s to %s.",
        recordToString(CurrentRecord).data(), recordToString(To).data());

  CurrentRecord = To;
  return Error::success();
} // namespace xray

Error BlockVerifier::visit(BufferExtents &) {
  return transition(State::BufferExtents);
}

Error BlockVerifier::visit(WallclockRecord &) {
  return transition(State::WallClockTime);
}

Error BlockVerifier::visit(NewCPUIDRecord &) {
  return transition(State::NewCPUId);
}

Error BlockVerifier::visit(TSCWrapRecord &) {
  return transition(State::TSCWrap);
}

Error BlockVerifier::visit(CustomEventRecord &) {
  return transition(State::CustomEvent);
}

Error BlockVerifier::visit(CustomEventRecordV5 &) {
  return transition(State::CustomEvent);
}

Error BlockVerifier::visit(TypedEventRecord &) {
  return transition(State::TypedEvent);
}

Error BlockVerifier::visit(CallArgRecord &) {
  return transition(State::CallArg);
}

Error BlockVerifier::visit(PIDRecord &) { return transition(State::PIDEntry); }

Error BlockVerifier::visit(NewBufferRecord &) {
  return transition(State::NewBuffer);
}

Error BlockVerifier::visit(EndBufferRecord &) {
  return transition(State::EndOfBuffer);
}

Error BlockVerifier::visit(FunctionRecord &) {
  return transition(State::Function);
}

Error BlockVerifier::verify() {
  // The known terminal conditions are the following:
  switch (CurrentRecord) {
  case State::EndOfBuffer:
  case State::NewCPUId:
  case State::CustomEvent:
  case State::TypedEvent:
  case State::Function:
  case State::CallArg:
  case State::TSCWrap:
    return Error::success();
  default:
    return createStringError(
        std::make_error_code(std::errc::executable_format_error),
        "BlockVerifier: Invalid terminal condition %s, malformed block.",
        recordToString(CurrentRecord).data());
  }
}

void BlockVerifier::reset() { CurrentRecord = State::Unknown; }

} // namespace xray
} // namespace llvm
