brintos

brintos / llvm-project-archived public Read only

0
0
Text · 14.5 KiB · c6164b6 Raw
435 lines · cpp
1//===-- BenchmarkResult.cpp -------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "BenchmarkResult.h"10#include "BenchmarkRunner.h"11#include "Error.h"12#include "ValidationEvent.h"13#include "llvm/ADT/STLExtras.h"14#include "llvm/ADT/ScopeExit.h"15#include "llvm/ADT/StringRef.h"16#include "llvm/ADT/bit.h"17#include "llvm/ObjectYAML/YAML.h"18#include "llvm/Support/Errc.h"19#include "llvm/Support/FileOutputBuffer.h"20#include "llvm/Support/FileSystem.h"21#include "llvm/Support/Format.h"22#include "llvm/Support/raw_ostream.h"23 24static constexpr char kIntegerPrefix[] = "i_0x";25static constexpr char kDoublePrefix[] = "f_";26static constexpr char kInvalidOperand[] = "INVALID";27 28namespace llvm {29 30namespace {31 32// A mutable struct holding an LLVMState that can be passed through the33// serialization process to encode/decode registers and instructions.34struct YamlContext {35  YamlContext(const exegesis::LLVMState &State)36      : State(&State), ErrorStream(LastError),37        OpcodeNameToOpcodeIdx(State.getOpcodeNameToOpcodeIdxMapping()) {}38 39  void serializeMCInst(const MCInst &MCInst, raw_ostream &OS) {40    OS << getInstrName(MCInst.getOpcode());41    for (const auto &Op : MCInst) {42      OS << ' ';43      serializeMCOperand(Op, OS);44    }45  }46 47  void deserializeMCInst(StringRef String, MCInst &Value) {48    SmallVector<StringRef, 16> Pieces;49    String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);50    if (Pieces.empty()) {51      ErrorStream << "Unknown Instruction: '" << String << "'\n";52      return;53    }54    bool ProcessOpcode = true;55    for (StringRef Piece : Pieces) {56      if (ProcessOpcode)57        Value.setOpcode(getInstrOpcode(Piece));58      else59        Value.addOperand(deserializeMCOperand(Piece));60      ProcessOpcode = false;61    }62  }63 64  std::string &getLastError() { return ErrorStream.str(); }65 66  raw_string_ostream &getErrorStream() { return ErrorStream; }67 68  StringRef getRegName(MCRegister Reg) {69    // Special case: Reg may be invalid. We have to deal with it explicitly.70    if (!Reg.isValid())71      return kNoRegister;72    const StringRef RegName = State->getRegInfo().getName(Reg);73    if (RegName.empty())74      ErrorStream << "No register with enum value '" << Reg.id() << "'\n";75    return RegName;76  }77 78  std::optional<MCRegister> getRegNo(StringRef RegName) {79    std::optional<MCRegister> RegisterNumber =80        State->getRegisterNumberFromName(RegName);81    if (!RegisterNumber.has_value())82      ErrorStream << "No register with name '" << RegName << "'\n";83    return RegisterNumber;84  }85 86private:87  void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {88    OS << kIntegerPrefix;89    OS.write_hex(bit_cast<uint64_t>(Value));90  }91 92  bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) {93    if (!String.consume_front(kIntegerPrefix))94      return false;95    return !String.consumeInteger(16, Value);96  }97 98  void serializeFPOperand(raw_ostream &OS, double Value) {99    OS << kDoublePrefix << format("%la", Value);100  }101 102  bool tryDeserializeFPOperand(StringRef String, double &Value) {103    if (!String.consume_front(kDoublePrefix))104      return false;105    char *EndPointer = nullptr;106    Value = strtod(String.begin(), &EndPointer);107    return EndPointer == String.end();108  }109 110  void serializeMCOperand(const MCOperand &MCOperand, raw_ostream &OS) {111    if (MCOperand.isReg()) {112      OS << getRegName(MCOperand.getReg());113    } else if (MCOperand.isImm()) {114      serializeIntegerOperand(OS, MCOperand.getImm());115    } else if (MCOperand.isDFPImm()) {116      serializeFPOperand(OS, bit_cast<double>(MCOperand.getDFPImm()));117    } else {118      OS << kInvalidOperand;119    }120  }121 122  MCOperand deserializeMCOperand(StringRef String) {123    assert(!String.empty());124    int64_t IntValue = 0;125    double DoubleValue = 0;126    if (tryDeserializeIntegerOperand(String, IntValue))127      return MCOperand::createImm(IntValue);128    if (tryDeserializeFPOperand(String, DoubleValue))129      return MCOperand::createDFPImm(bit_cast<uint64_t>(DoubleValue));130    if (auto RegNo = getRegNo(String))131      return MCOperand::createReg(*RegNo);132    if (String != kInvalidOperand)133      ErrorStream << "Unknown Operand: '" << String << "'\n";134    return {};135  }136 137  StringRef getInstrName(unsigned InstrNo) {138    const StringRef InstrName = State->getInstrInfo().getName(InstrNo);139    if (InstrName.empty())140      ErrorStream << "No opcode with enum value '" << InstrNo << "'\n";141    return InstrName;142  }143 144  unsigned getInstrOpcode(StringRef InstrName) {145    auto Iter = OpcodeNameToOpcodeIdx.find(InstrName);146    if (Iter != OpcodeNameToOpcodeIdx.end())147      return Iter->second;148    ErrorStream << "No opcode with name '" << InstrName << "'\n";149    return 0;150  }151 152  const exegesis::LLVMState *State;153  std::string LastError;154  raw_string_ostream ErrorStream;155  const DenseMap<StringRef, unsigned> &OpcodeNameToOpcodeIdx;156};157} // namespace158 159// Defining YAML traits for IO.160namespace yaml {161 162static YamlContext &getTypedContext(void *Ctx) {163  return *reinterpret_cast<YamlContext *>(Ctx);164}165 166// std::vector<MCInst> will be rendered as a list.167template <> struct SequenceElementTraits<MCInst> {168  static const bool flow = false;169};170 171template <> struct ScalarTraits<MCInst> {172 173  static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) {174    getTypedContext(Ctx).serializeMCInst(Value, Out);175  }176 177  static StringRef input(StringRef Scalar, void *Ctx, MCInst &Value) {178    YamlContext &Context = getTypedContext(Ctx);179    Context.deserializeMCInst(Scalar, Value);180    return Context.getLastError();181  }182 183  // By default strings are quoted only when necessary.184  // We force the use of single quotes for uniformity.185  static QuotingType mustQuote(StringRef) { return QuotingType::Single; }186 187  static const bool flow = true;188};189 190// std::vector<exegesis::Measure> will be rendered as a list.191template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {192  static const bool flow = false;193};194 195template <>196struct CustomMappingTraits<std::map<exegesis::ValidationEvent, int64_t>> {197  static void inputOne(IO &Io, StringRef KeyStr,198                       std::map<exegesis::ValidationEvent, int64_t> &VI) {199    Expected<exegesis::ValidationEvent> Key =200        exegesis::getValidationEventByName(KeyStr);201    if (!Key) {202      Io.setError("Key is not a valid validation event");203      return;204    }205    Io.mapRequired(KeyStr, VI[*Key]);206  }207 208  static void output(IO &Io, std::map<exegesis::ValidationEvent, int64_t> &VI) {209    for (auto &IndividualVI : VI) {210      Io.mapRequired(exegesis::getValidationEventName(IndividualVI.first),211                     IndividualVI.second);212    }213  }214};215 216// exegesis::Measure is rendererd as a flow instead of a list.217// e.g. { "key": "the key", "value": 0123 }218template <> struct MappingTraits<exegesis::BenchmarkMeasure> {219  static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) {220    Io.mapRequired("key", Obj.Key);221    if (!Io.outputting()) {222      // For backward compatibility, interpret debug_string as a key.223      Io.mapOptional("debug_string", Obj.Key);224    }225    Io.mapRequired("value", Obj.PerInstructionValue);226    Io.mapOptional("per_snippet_value", Obj.PerSnippetValue);227    Io.mapOptional("validation_counters", Obj.ValidationCounters);228  }229  static const bool flow = true;230};231 232template <> struct ScalarEnumerationTraits<exegesis::Benchmark::ModeE> {233  static void enumeration(IO &Io, exegesis::Benchmark::ModeE &Value) {234    Io.enumCase(Value, "", exegesis::Benchmark::Unknown);235    Io.enumCase(Value, "latency", exegesis::Benchmark::Latency);236    Io.enumCase(Value, "uops", exegesis::Benchmark::Uops);237    Io.enumCase(Value, "inverse_throughput",238                exegesis::Benchmark::InverseThroughput);239  }240};241 242// std::vector<exegesis::RegisterValue> will be rendered as a list.243template <> struct SequenceElementTraits<exegesis::RegisterValue> {244  static const bool flow = false;245};246 247template <> struct ScalarTraits<exegesis::RegisterValue> {248  static constexpr unsigned kRadix = 16;249  static constexpr bool kSigned = false;250 251  static void output(const exegesis::RegisterValue &RV, void *Ctx,252                     raw_ostream &Out) {253    YamlContext &Context = getTypedContext(Ctx);254    Out << Context.getRegName(RV.Register) << "=0x"255        << toString(RV.Value, kRadix, kSigned);256  }257 258  static StringRef input(StringRef String, void *Ctx,259                         exegesis::RegisterValue &RV) {260    SmallVector<StringRef, 2> Pieces;261    String.split(Pieces, "=0x", /* MaxSplit */ -1,262                 /* KeepEmpty */ false);263    YamlContext &Context = getTypedContext(Ctx);264    std::optional<MCRegister> RegNo;265    if (Pieces.size() == 2 && (RegNo = Context.getRegNo(Pieces[0]))) {266      RV.Register = *RegNo;267      const unsigned BitsNeeded = APInt::getBitsNeeded(Pieces[1], kRadix);268      RV.Value = APInt(BitsNeeded, Pieces[1], kRadix);269    } else {270      Context.getErrorStream()271          << "Unknown initial register value: '" << String << "'";272    }273    return Context.getLastError();274  }275 276  static QuotingType mustQuote(StringRef) { return QuotingType::Single; }277 278  static const bool flow = true;279};280 281template <> struct MappingContextTraits<exegesis::BenchmarkKey, YamlContext> {282  static void mapping(IO &Io, exegesis::BenchmarkKey &Obj,283                      YamlContext &Context) {284    Io.setContext(&Context);285    Io.mapRequired("instructions", Obj.Instructions);286    Io.mapOptional("config", Obj.Config);287    Io.mapRequired("register_initial_values", Obj.RegisterInitialValues);288  }289};290 291template <> struct MappingContextTraits<exegesis::Benchmark, YamlContext> {292  struct NormalizedBinary {293    NormalizedBinary(IO &io) {}294    NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {}295    std::vector<uint8_t> denormalize(IO &) {296      std::vector<uint8_t> Data;297      std::string Str;298      raw_string_ostream OSS(Str);299      Binary.writeAsBinary(OSS);300      OSS.flush();301      Data.assign(Str.begin(), Str.end());302      return Data;303    }304 305    BinaryRef Binary;306  };307 308  static void mapping(IO &Io, exegesis::Benchmark &Obj, YamlContext &Context) {309    Io.mapRequired("mode", Obj.Mode);310    Io.mapRequired("key", Obj.Key, Context);311    Io.mapRequired("cpu_name", Obj.CpuName);312    Io.mapRequired("llvm_triple", Obj.LLVMTriple);313    // Optionally map num_repetitions and min_instructions to the same314    // value to preserve backwards compatibility.315    // TODO(boomanaiden154): Move min_instructions to mapRequired and316    // remove num_repetitions once num_repetitions is ready to be removed317    // completely.318    if (Io.outputting())319      Io.mapRequired("min_instructions", Obj.MinInstructions);320    else {321      Io.mapOptional("num_repetitions", Obj.MinInstructions);322      Io.mapOptional("min_instructions", Obj.MinInstructions);323    }324    Io.mapRequired("measurements", Obj.Measurements);325    Io.mapRequired("error", Obj.Error);326    Io.mapOptional("info", Obj.Info);327    // AssembledSnippet328    MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(329        Io, Obj.AssembledSnippet);330    Io.mapOptional("assembled_snippet", BinaryString->Binary);331  }332};333 334template <> struct MappingTraits<exegesis::Benchmark::TripleAndCpu> {335  static void mapping(IO &Io, exegesis::Benchmark::TripleAndCpu &Obj) {336    assert(!Io.outputting() && "can only read TripleAndCpu");337    // Read triple.338    Io.mapRequired("llvm_triple", Obj.LLVMTriple);339    Io.mapRequired("cpu_name", Obj.CpuName);340    // Drop everything else.341  }342};343 344} // namespace yaml345 346namespace exegesis {347 348Expected<std::set<Benchmark::TripleAndCpu>>349Benchmark::readTriplesAndCpusFromYamls(MemoryBufferRef Buffer) {350  // We're only mapping a field, drop other fields and silence the corresponding351  // warnings.352  yaml::Input Yin(Buffer, nullptr, +[](const SMDiagnostic &, void *Context) {});353  Yin.setAllowUnknownKeys(true);354  std::set<TripleAndCpu> Result;355  yaml::EmptyContext Context;356  while (Yin.setCurrentDocument()) {357    TripleAndCpu TC;358    yamlize(Yin, TC, /*unused*/ true, Context);359    if (Yin.error())360      return errorCodeToError(Yin.error());361    Result.insert(TC);362    Yin.nextDocument();363  }364  return Result;365}366 367Expected<Benchmark> Benchmark::readYaml(const LLVMState &State,368                                        MemoryBufferRef Buffer) {369  yaml::Input Yin(Buffer);370  YamlContext Context(State);371  Benchmark Benchmark;372  if (Yin.setCurrentDocument())373    yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context);374  if (!Context.getLastError().empty())375    return make_error<Failure>(Context.getLastError());376  return std::move(Benchmark);377}378 379Expected<std::vector<Benchmark>> Benchmark::readYamls(const LLVMState &State,380                                                      MemoryBufferRef Buffer) {381  yaml::Input Yin(Buffer);382  YamlContext Context(State);383  std::vector<Benchmark> Benchmarks;384  while (Yin.setCurrentDocument()) {385    Benchmarks.emplace_back();386    yamlize(Yin, Benchmarks.back(), /*unused*/ true, Context);387    if (Yin.error())388      return errorCodeToError(Yin.error());389    if (!Context.getLastError().empty())390      return make_error<Failure>(Context.getLastError());391    Yin.nextDocument();392  }393  return std::move(Benchmarks);394}395 396Error Benchmark::writeYamlTo(const LLVMState &State, raw_ostream &OS) {397  auto Cleanup = make_scope_exit([&] { OS.flush(); });398  yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/);399  YamlContext Context(State);400  Yout.beginDocuments();401  yaml::yamlize(Yout, *this, /*unused*/ true, Context);402  if (!Context.getLastError().empty())403    return make_error<Failure>(Context.getLastError());404  Yout.endDocuments();405  return Error::success();406}407 408Error Benchmark::readYamlFrom(const LLVMState &State, StringRef InputContent) {409  yaml::Input Yin(InputContent);410  YamlContext Context(State);411  if (Yin.setCurrentDocument())412    yaml::yamlize(Yin, *this, /*unused*/ true, Context);413  if (!Context.getLastError().empty())414    return make_error<Failure>(Context.getLastError());415  return Error::success();416}417 418void PerInstructionStats::push(const BenchmarkMeasure &BM) {419  if (Key.empty())420    Key = BM.Key;421  assert(Key == BM.Key);422  ++NumValues;423  SumValues += BM.PerInstructionValue;424  MaxValue = std::max(MaxValue, BM.PerInstructionValue);425  MinValue = std::min(MinValue, BM.PerInstructionValue);426}427 428bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) {429  return std::tie(A.Key, A.PerInstructionValue, A.PerSnippetValue) ==430         std::tie(B.Key, B.PerInstructionValue, B.PerSnippetValue);431}432 433} // namespace exegesis434} // namespace llvm435