//===-- BenchmarkResult.cpp -------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "BenchmarkResult.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ObjectYAML/YAML.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"

static constexpr const char kIntegerFormat[] = "i_0x%" PRId64 "x";
static constexpr const char kDoubleFormat[] = "f_%la";

static void serialize(const exegesis::BenchmarkResultContext &Context,
                      const llvm::MCOperand &MCOperand, llvm::raw_ostream &OS) {
  if (MCOperand.isReg()) {
    OS << Context.getRegName(MCOperand.getReg());
  } else if (MCOperand.isImm()) {
    OS << llvm::format(kIntegerFormat, MCOperand.getImm());
  } else if (MCOperand.isFPImm()) {
    OS << llvm::format(kDoubleFormat, MCOperand.getFPImm());
  } else {
    OS << "INVALID";
  }
}

static void serialize(const exegesis::BenchmarkResultContext &Context,
                      const llvm::MCInst &MCInst, llvm::raw_ostream &OS) {
  OS << Context.getInstrName(MCInst.getOpcode());
  for (const auto &Op : MCInst) {
    OS << ' ';
    serialize(Context, Op, OS);
  }
}

static llvm::MCOperand
deserialize(const exegesis::BenchmarkResultContext &Context,
            llvm::StringRef String) {
  assert(!String.empty());
  int64_t IntValue = 0;
  double DoubleValue = 0;
  if (sscanf(String.data(), kIntegerFormat, &IntValue) == 1)
    return llvm::MCOperand::createImm(IntValue);
  if (sscanf(String.data(), kDoubleFormat, &DoubleValue) == 1)
    return llvm::MCOperand::createFPImm(DoubleValue);
  if (unsigned RegNo = Context.getRegNo(String)) // Returns 0 if invalid.
    return llvm::MCOperand::createReg(RegNo);
  return {};
}

static llvm::StringRef
deserialize(const exegesis::BenchmarkResultContext &Context,
            llvm::StringRef String, llvm::MCInst &Value) {
  llvm::SmallVector<llvm::StringRef, 8> Pieces;
  String.split(Pieces, " ");
  if (Pieces.empty())
    return "Invalid Instruction";
  bool ProcessOpcode = true;
  for (llvm::StringRef Piece : Pieces) {
    if (ProcessOpcode) {
      ProcessOpcode = false;
      Value.setOpcode(Context.getInstrOpcode(Piece));
      if (Value.getOpcode() == 0)
        return "Unknown Opcode Name";
    } else {
      Value.addOperand(deserialize(Context, Piece));
    }
  }
  return {};
}

// YAML IO requires a mutable pointer to Context but we guarantee to not
// modify it.
static void *getUntypedContext(const exegesis::BenchmarkResultContext &Ctx) {
  return const_cast<exegesis::BenchmarkResultContext *>(&Ctx);
}

static const exegesis::BenchmarkResultContext &getTypedContext(void *Ctx) {
  assert(Ctx);
  return *static_cast<const exegesis::BenchmarkResultContext *>(Ctx);
}

// Defining YAML traits for IO.
namespace llvm {
namespace yaml {

// std::vector<llvm::MCInst> will be rendered as a list.
template <> struct SequenceElementTraits<llvm::MCInst> {
  static const bool flow = false;
};

template <> struct ScalarTraits<llvm::MCInst> {

  static void output(const llvm::MCInst &Value, void *Ctx,
                     llvm::raw_ostream &Out) {
    serialize(getTypedContext(Ctx), Value, Out);
  }

  static StringRef input(StringRef Scalar, void *Ctx, llvm::MCInst &Value) {
    return deserialize(getTypedContext(Ctx), Scalar, Value);
  }

  static QuotingType mustQuote(StringRef) { return QuotingType::Single; }

  static const bool flow = true;
};

// std::vector<exegesis::Measure> will be rendered as a list.
template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {
  static const bool flow = false;
};

// exegesis::Measure is rendererd as a flow instead of a list.
// e.g. { "key": "the key", "value": 0123 }
template <> struct MappingTraits<exegesis::BenchmarkMeasure> {
  static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) {
    Io.mapRequired("key", Obj.Key);
    Io.mapRequired("value", Obj.Value);
    Io.mapOptional("debug_string", Obj.DebugString);
  }
  static const bool flow = true;
};

template <>
struct ScalarEnumerationTraits<exegesis::InstructionBenchmark::ModeE> {
  static void enumeration(IO &Io,
                          exegesis::InstructionBenchmark::ModeE &Value) {
    Io.enumCase(Value, "", exegesis::InstructionBenchmark::Unknown);
    Io.enumCase(Value, "latency", exegesis::InstructionBenchmark::Latency);
    Io.enumCase(Value, "uops", exegesis::InstructionBenchmark::Uops);
  }
};

template <> struct MappingTraits<exegesis::InstructionBenchmarkKey> {
  static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj) {
    Io.mapRequired("instructions", Obj.Instructions);
    Io.mapOptional("config", Obj.Config);
  }
};

template <> struct MappingTraits<exegesis::InstructionBenchmark> {
  class NormalizedBinary {
  public:
    NormalizedBinary(IO &io) {}
    NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {}
    std::vector<uint8_t> denormalize(IO &) {
      std::vector<uint8_t> Data;
      std::string Str;
      raw_string_ostream OSS(Str);
      Binary.writeAsBinary(OSS);
      OSS.flush();
      Data.assign(Str.begin(), Str.end());
      return Data;
    }

    BinaryRef Binary;
  };

  static void mapping(IO &Io, exegesis::InstructionBenchmark &Obj) {
    Io.mapRequired("mode", Obj.Mode);
    Io.mapRequired("key", Obj.Key);
    Io.mapRequired("cpu_name", Obj.CpuName);
    Io.mapRequired("llvm_triple", Obj.LLVMTriple);
    Io.mapRequired("num_repetitions", Obj.NumRepetitions);
    Io.mapRequired("measurements", Obj.Measurements);
    Io.mapRequired("error", Obj.Error);
    Io.mapOptional("info", Obj.Info);
    // AssembledSnippet
    MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(
        Io, Obj.AssembledSnippet);
    Io.mapOptional("assembled_snippet", BinaryString->Binary);
  }
};

} // namespace yaml
} // namespace llvm

LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(exegesis::InstructionBenchmark)

namespace exegesis {

void BenchmarkResultContext::addRegEntry(unsigned RegNo, llvm::StringRef Name) {
  assert(RegNoToName.find(RegNo) == RegNoToName.end());
  assert(RegNameToNo.find(Name) == RegNameToNo.end());
  RegNoToName[RegNo] = Name;
  RegNameToNo[Name] = RegNo;
}

llvm::StringRef BenchmarkResultContext::getRegName(unsigned RegNo) const {
  const auto Itr = RegNoToName.find(RegNo);
  if (Itr != RegNoToName.end())
    return Itr->second;
  return {};
}

unsigned BenchmarkResultContext::getRegNo(llvm::StringRef Name) const {
  const auto Itr = RegNameToNo.find(Name);
  if (Itr != RegNameToNo.end())
    return Itr->second;
  return 0;
}

void BenchmarkResultContext::addInstrEntry(unsigned Opcode,
                                           llvm::StringRef Name) {
  assert(InstrOpcodeToName.find(Opcode) == InstrOpcodeToName.end());
  assert(InstrNameToOpcode.find(Name) == InstrNameToOpcode.end());
  InstrOpcodeToName[Opcode] = Name;
  InstrNameToOpcode[Name] = Opcode;
}

llvm::StringRef BenchmarkResultContext::getInstrName(unsigned Opcode) const {
  const auto Itr = InstrOpcodeToName.find(Opcode);
  if (Itr != InstrOpcodeToName.end())
    return Itr->second;
  return {};
}

unsigned BenchmarkResultContext::getInstrOpcode(llvm::StringRef Name) const {
  const auto Itr = InstrNameToOpcode.find(Name);
  if (Itr != InstrNameToOpcode.end())
    return Itr->second;
  return 0;
}

template <typename ObjectOrList>
static llvm::Expected<ObjectOrList>
readYamlCommon(const BenchmarkResultContext &Context,
               llvm::StringRef Filename) {
  if (auto ExpectedMemoryBuffer =
          llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) {
    std::unique_ptr<llvm::MemoryBuffer> MemoryBuffer =
        std::move(ExpectedMemoryBuffer.get());
    llvm::yaml::Input Yin(*MemoryBuffer, getUntypedContext(Context));
    ObjectOrList Benchmark;
    Yin >> Benchmark;
    return Benchmark;
  } else {
    return ExpectedMemoryBuffer.takeError();
  }
}

llvm::Expected<InstructionBenchmark>
InstructionBenchmark::readYaml(const BenchmarkResultContext &Context,
                               llvm::StringRef Filename) {
  return readYamlCommon<InstructionBenchmark>(Context, Filename);
}

llvm::Expected<std::vector<InstructionBenchmark>>
InstructionBenchmark::readYamls(const BenchmarkResultContext &Context,
                                llvm::StringRef Filename) {
  return readYamlCommon<std::vector<InstructionBenchmark>>(Context, Filename);
}

void InstructionBenchmark::writeYamlTo(const BenchmarkResultContext &Context,
                                       llvm::raw_ostream &OS) {
  llvm::yaml::Output Yout(OS, getUntypedContext(Context));
  Yout << *this;
}

void InstructionBenchmark::readYamlFrom(const BenchmarkResultContext &Context,
                                        llvm::StringRef InputContent) {
  llvm::yaml::Input Yin(InputContent, getUntypedContext(Context));
  Yin >> *this;
}

llvm::Error
InstructionBenchmark::writeYaml(const BenchmarkResultContext &Context,
                                const llvm::StringRef Filename) {
  if (Filename == "-") {
    writeYamlTo(Context, llvm::outs());
  } else {
    int ResultFD = 0;
    if (auto E = llvm::errorCodeToError(
            openFileForWrite(Filename, ResultFD, llvm::sys::fs::CD_CreateAlways,
                             llvm::sys::fs::F_Text))) {
      return E;
    }
    llvm::raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/);
    writeYamlTo(Context, Ostr);
  }
  return llvm::Error::success();
}

void BenchmarkMeasureStats::push(const BenchmarkMeasure &BM) {
  if (Key.empty())
    Key = BM.Key;
  assert(Key == BM.Key);
  ++NumValues;
  SumValues += BM.Value;
  MaxValue = std::max(MaxValue, BM.Value);
  MinValue = std::min(MinValue, BM.Value);
}

} // namespace exegesis
