Home | History | Annotate | Line # | Download | only in lib
      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-exception
      6 //
      7 //===----------------------------------------------------------------------===//
      8 
      9 #include "BenchmarkResult.h"
     10 #include "BenchmarkRunner.h"
     11 #include "Error.h"
     12 #include "llvm/ADT/STLExtras.h"
     13 #include "llvm/ADT/ScopeExit.h"
     14 #include "llvm/ADT/StringMap.h"
     15 #include "llvm/ADT/StringRef.h"
     16 #include "llvm/ADT/bit.h"
     17 #include "llvm/ObjectYAML/YAML.h"
     18 #include "llvm/Support/FileOutputBuffer.h"
     19 #include "llvm/Support/FileSystem.h"
     20 #include "llvm/Support/Format.h"
     21 #include "llvm/Support/raw_ostream.h"
     22 
     23 static constexpr const char kIntegerPrefix[] = "i_0x";
     24 static constexpr const char kDoublePrefix[] = "f_";
     25 static constexpr const char kInvalidOperand[] = "INVALID";
     26 static constexpr llvm::StringLiteral kNoRegister("%noreg");
     27 
     28 namespace llvm {
     29 
     30 namespace {
     31 
     32 // A mutable struct holding an LLVMState that can be passed through the
     33 // serialization process to encode/decode registers and instructions.
     34 struct YamlContext {
     35   YamlContext(const exegesis::LLVMState &State)
     36       : State(&State), ErrorStream(LastError),
     37         OpcodeNameToOpcodeIdx(
     38             generateOpcodeNameToOpcodeIdxMapping(State.getInstrInfo())),
     39         RegNameToRegNo(generateRegNameToRegNoMapping(State.getRegInfo())) {}
     40 
     41   static StringMap<unsigned>
     42   generateOpcodeNameToOpcodeIdxMapping(const MCInstrInfo &InstrInfo) {
     43     StringMap<unsigned> Map(InstrInfo.getNumOpcodes());
     44     for (unsigned I = 0, E = InstrInfo.getNumOpcodes(); I < E; ++I)
     45       Map[InstrInfo.getName(I)] = I;
     46     assert(Map.size() == InstrInfo.getNumOpcodes() && "Size prediction failed");
     47     return Map;
     48   };
     49 
     50   StringMap<unsigned>
     51   generateRegNameToRegNoMapping(const MCRegisterInfo &RegInfo) {
     52     StringMap<unsigned> Map(RegInfo.getNumRegs());
     53     // Special-case RegNo 0, which would otherwise be spelled as ''.
     54     Map[kNoRegister] = 0;
     55     for (unsigned I = 1, E = RegInfo.getNumRegs(); I < E; ++I)
     56       Map[RegInfo.getName(I)] = I;
     57     assert(Map.size() == RegInfo.getNumRegs() && "Size prediction failed");
     58     return Map;
     59   };
     60 
     61   void serializeMCInst(const MCInst &MCInst, raw_ostream &OS) {
     62     OS << getInstrName(MCInst.getOpcode());
     63     for (const auto &Op : MCInst) {
     64       OS << ' ';
     65       serializeMCOperand(Op, OS);
     66     }
     67   }
     68 
     69   void deserializeMCInst(StringRef String, MCInst &Value) {
     70     SmallVector<StringRef, 16> Pieces;
     71     String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
     72     if (Pieces.empty()) {
     73       ErrorStream << "Unknown Instruction: '" << String << "'\n";
     74       return;
     75     }
     76     bool ProcessOpcode = true;
     77     for (StringRef Piece : Pieces) {
     78       if (ProcessOpcode)
     79         Value.setOpcode(getInstrOpcode(Piece));
     80       else
     81         Value.addOperand(deserializeMCOperand(Piece));
     82       ProcessOpcode = false;
     83     }
     84   }
     85 
     86   std::string &getLastError() { return ErrorStream.str(); }
     87 
     88   raw_string_ostream &getErrorStream() { return ErrorStream; }
     89 
     90   StringRef getRegName(unsigned RegNo) {
     91     // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
     92     if (RegNo == 0)
     93       return kNoRegister;
     94     const StringRef RegName = State->getRegInfo().getName(RegNo);
     95     if (RegName.empty())
     96       ErrorStream << "No register with enum value '" << RegNo << "'\n";
     97     return RegName;
     98   }
     99 
    100   Optional<unsigned> getRegNo(StringRef RegName) {
    101     auto Iter = RegNameToRegNo.find(RegName);
    102     if (Iter != RegNameToRegNo.end())
    103       return Iter->second;
    104     ErrorStream << "No register with name '" << RegName << "'\n";
    105     return None;
    106   }
    107 
    108 private:
    109   void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {
    110     OS << kIntegerPrefix;
    111     OS.write_hex(bit_cast<uint64_t>(Value));
    112   }
    113 
    114   bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) {
    115     if (!String.consume_front(kIntegerPrefix))
    116       return false;
    117     return !String.consumeInteger(16, Value);
    118   }
    119 
    120   void serializeFPOperand(raw_ostream &OS, double Value) {
    121     OS << kDoublePrefix << format("%la", Value);
    122   }
    123 
    124   bool tryDeserializeFPOperand(StringRef String, double &Value) {
    125     if (!String.consume_front(kDoublePrefix))
    126       return false;
    127     char *EndPointer = nullptr;
    128     Value = strtod(String.begin(), &EndPointer);
    129     return EndPointer == String.end();
    130   }
    131 
    132   void serializeMCOperand(const MCOperand &MCOperand, raw_ostream &OS) {
    133     if (MCOperand.isReg()) {
    134       OS << getRegName(MCOperand.getReg());
    135     } else if (MCOperand.isImm()) {
    136       serializeIntegerOperand(OS, MCOperand.getImm());
    137     } else if (MCOperand.isDFPImm()) {
    138       serializeFPOperand(OS, bit_cast<double>(MCOperand.getDFPImm()));
    139     } else {
    140       OS << kInvalidOperand;
    141     }
    142   }
    143 
    144   MCOperand deserializeMCOperand(StringRef String) {
    145     assert(!String.empty());
    146     int64_t IntValue = 0;
    147     double DoubleValue = 0;
    148     if (tryDeserializeIntegerOperand(String, IntValue))
    149       return MCOperand::createImm(IntValue);
    150     if (tryDeserializeFPOperand(String, DoubleValue))
    151       return MCOperand::createDFPImm(bit_cast<uint64_t>(DoubleValue));
    152     if (auto RegNo = getRegNo(String))
    153       return MCOperand::createReg(*RegNo);
    154     if (String != kInvalidOperand)
    155       ErrorStream << "Unknown Operand: '" << String << "'\n";
    156     return {};
    157   }
    158 
    159   StringRef getInstrName(unsigned InstrNo) {
    160     const StringRef InstrName = State->getInstrInfo().getName(InstrNo);
    161     if (InstrName.empty())
    162       ErrorStream << "No opcode with enum value '" << InstrNo << "'\n";
    163     return InstrName;
    164   }
    165 
    166   unsigned getInstrOpcode(StringRef InstrName) {
    167     auto Iter = OpcodeNameToOpcodeIdx.find(InstrName);
    168     if (Iter != OpcodeNameToOpcodeIdx.end())
    169       return Iter->second;
    170     ErrorStream << "No opcode with name '" << InstrName << "'\n";
    171     return 0;
    172   }
    173 
    174   const exegesis::LLVMState *State;
    175   std::string LastError;
    176   raw_string_ostream ErrorStream;
    177   const StringMap<unsigned> OpcodeNameToOpcodeIdx;
    178   const StringMap<unsigned> RegNameToRegNo;
    179 };
    180 } // namespace
    181 
    182 // Defining YAML traits for IO.
    183 namespace yaml {
    184 
    185 static YamlContext &getTypedContext(void *Ctx) {
    186   return *reinterpret_cast<YamlContext *>(Ctx);
    187 }
    188 
    189 // std::vector<MCInst> will be rendered as a list.
    190 template <> struct SequenceElementTraits<MCInst> {
    191   static const bool flow = false;
    192 };
    193 
    194 template <> struct ScalarTraits<MCInst> {
    195 
    196   static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) {
    197     getTypedContext(Ctx).serializeMCInst(Value, Out);
    198   }
    199 
    200   static StringRef input(StringRef Scalar, void *Ctx, MCInst &Value) {
    201     YamlContext &Context = getTypedContext(Ctx);
    202     Context.deserializeMCInst(Scalar, Value);
    203     return Context.getLastError();
    204   }
    205 
    206   // By default strings are quoted only when necessary.
    207   // We force the use of single quotes for uniformity.
    208   static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
    209 
    210   static const bool flow = true;
    211 };
    212 
    213 // std::vector<exegesis::Measure> will be rendered as a list.
    214 template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {
    215   static const bool flow = false;
    216 };
    217 
    218 // exegesis::Measure is rendererd as a flow instead of a list.
    219 // e.g. { "key": "the key", "value": 0123 }
    220 template <> struct MappingTraits<exegesis::BenchmarkMeasure> {
    221   static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) {
    222     Io.mapRequired("key", Obj.Key);
    223     if (!Io.outputting()) {
    224       // For backward compatibility, interpret debug_string as a key.
    225       Io.mapOptional("debug_string", Obj.Key);
    226     }
    227     Io.mapRequired("value", Obj.PerInstructionValue);
    228     Io.mapOptional("per_snippet_value", Obj.PerSnippetValue);
    229   }
    230   static const bool flow = true;
    231 };
    232 
    233 template <>
    234 struct ScalarEnumerationTraits<exegesis::InstructionBenchmark::ModeE> {
    235   static void enumeration(IO &Io,
    236                           exegesis::InstructionBenchmark::ModeE &Value) {
    237     Io.enumCase(Value, "", exegesis::InstructionBenchmark::Unknown);
    238     Io.enumCase(Value, "latency", exegesis::InstructionBenchmark::Latency);
    239     Io.enumCase(Value, "uops", exegesis::InstructionBenchmark::Uops);
    240     Io.enumCase(Value, "inverse_throughput",
    241                 exegesis::InstructionBenchmark::InverseThroughput);
    242   }
    243 };
    244 
    245 // std::vector<exegesis::RegisterValue> will be rendered as a list.
    246 template <> struct SequenceElementTraits<exegesis::RegisterValue> {
    247   static const bool flow = false;
    248 };
    249 
    250 template <> struct ScalarTraits<exegesis::RegisterValue> {
    251   static constexpr const unsigned kRadix = 16;
    252   static constexpr const bool kSigned = false;
    253 
    254   static void output(const exegesis::RegisterValue &RV, void *Ctx,
    255                      raw_ostream &Out) {
    256     YamlContext &Context = getTypedContext(Ctx);
    257     Out << Context.getRegName(RV.Register) << "=0x"
    258         << RV.Value.toString(kRadix, kSigned);
    259   }
    260 
    261   static StringRef input(StringRef String, void *Ctx,
    262                          exegesis::RegisterValue &RV) {
    263     SmallVector<StringRef, 2> Pieces;
    264     String.split(Pieces, "=0x", /* MaxSplit */ -1,
    265                  /* KeepEmpty */ false);
    266     YamlContext &Context = getTypedContext(Ctx);
    267     Optional<unsigned> RegNo;
    268     if (Pieces.size() == 2 && (RegNo = Context.getRegNo(Pieces[0]))) {
    269       RV.Register = *RegNo;
    270       const unsigned BitsNeeded = APInt::getBitsNeeded(Pieces[1], kRadix);
    271       RV.Value = APInt(BitsNeeded, Pieces[1], kRadix);
    272     } else {
    273       Context.getErrorStream()
    274           << "Unknown initial register value: '" << String << "'";
    275     }
    276     return Context.getLastError();
    277   }
    278 
    279   static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
    280 
    281   static const bool flow = true;
    282 };
    283 
    284 template <>
    285 struct MappingContextTraits<exegesis::InstructionBenchmarkKey, YamlContext> {
    286   static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj,
    287                       YamlContext &Context) {
    288     Io.setContext(&Context);
    289     Io.mapRequired("instructions", Obj.Instructions);
    290     Io.mapOptional("config", Obj.Config);
    291     Io.mapRequired("register_initial_values", Obj.RegisterInitialValues);
    292   }
    293 };
    294 
    295 template <>
    296 struct MappingContextTraits<exegesis::InstructionBenchmark, YamlContext> {
    297   struct NormalizedBinary {
    298     NormalizedBinary(IO &io) {}
    299     NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {}
    300     std::vector<uint8_t> denormalize(IO &) {
    301       std::vector<uint8_t> Data;
    302       std::string Str;
    303       raw_string_ostream OSS(Str);
    304       Binary.writeAsBinary(OSS);
    305       OSS.flush();
    306       Data.assign(Str.begin(), Str.end());
    307       return Data;
    308     }
    309 
    310     BinaryRef Binary;
    311   };
    312 
    313   static void mapping(IO &Io, exegesis::InstructionBenchmark &Obj,
    314                       YamlContext &Context) {
    315     Io.mapRequired("mode", Obj.Mode);
    316     Io.mapRequired("key", Obj.Key, Context);
    317     Io.mapRequired("cpu_name", Obj.CpuName);
    318     Io.mapRequired("llvm_triple", Obj.LLVMTriple);
    319     Io.mapRequired("num_repetitions", Obj.NumRepetitions);
    320     Io.mapRequired("measurements", Obj.Measurements);
    321     Io.mapRequired("error", Obj.Error);
    322     Io.mapOptional("info", Obj.Info);
    323     // AssembledSnippet
    324     MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(
    325         Io, Obj.AssembledSnippet);
    326     Io.mapOptional("assembled_snippet", BinaryString->Binary);
    327   }
    328 };
    329 
    330 } // namespace yaml
    331 
    332 namespace exegesis {
    333 
    334 Expected<InstructionBenchmark>
    335 InstructionBenchmark::readYaml(const LLVMState &State, StringRef Filename) {
    336   if (auto ExpectedMemoryBuffer =
    337           errorOrToExpected(MemoryBuffer::getFile(Filename, /*IsText=*/true))) {
    338     yaml::Input Yin(*ExpectedMemoryBuffer.get());
    339     YamlContext Context(State);
    340     InstructionBenchmark Benchmark;
    341     if (Yin.setCurrentDocument())
    342       yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context);
    343     if (!Context.getLastError().empty())
    344       return make_error<Failure>(Context.getLastError());
    345     return Benchmark;
    346   } else {
    347     return ExpectedMemoryBuffer.takeError();
    348   }
    349 }
    350 
    351 Expected<std::vector<InstructionBenchmark>>
    352 InstructionBenchmark::readYamls(const LLVMState &State, StringRef Filename) {
    353   if (auto ExpectedMemoryBuffer =
    354           errorOrToExpected(MemoryBuffer::getFile(Filename, /*IsText=*/true))) {
    355     yaml::Input Yin(*ExpectedMemoryBuffer.get());
    356     YamlContext Context(State);
    357     std::vector<InstructionBenchmark> Benchmarks;
    358     while (Yin.setCurrentDocument()) {
    359       Benchmarks.emplace_back();
    360       yamlize(Yin, Benchmarks.back(), /*unused*/ true, Context);
    361       if (Yin.error())
    362         return errorCodeToError(Yin.error());
    363       if (!Context.getLastError().empty())
    364         return make_error<Failure>(Context.getLastError());
    365       Yin.nextDocument();
    366     }
    367     return Benchmarks;
    368   } else {
    369     return ExpectedMemoryBuffer.takeError();
    370   }
    371 }
    372 
    373 Error InstructionBenchmark::writeYamlTo(const LLVMState &State,
    374                                         raw_ostream &OS) {
    375   auto Cleanup = make_scope_exit([&] { OS.flush(); });
    376   yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/);
    377   YamlContext Context(State);
    378   Yout.beginDocuments();
    379   yaml::yamlize(Yout, *this, /*unused*/ true, Context);
    380   if (!Context.getLastError().empty())
    381     return make_error<Failure>(Context.getLastError());
    382   Yout.endDocuments();
    383   return Error::success();
    384 }
    385 
    386 Error InstructionBenchmark::readYamlFrom(const LLVMState &State,
    387                                          StringRef InputContent) {
    388   yaml::Input Yin(InputContent);
    389   YamlContext Context(State);
    390   if (Yin.setCurrentDocument())
    391     yaml::yamlize(Yin, *this, /*unused*/ true, Context);
    392   if (!Context.getLastError().empty())
    393     return make_error<Failure>(Context.getLastError());
    394   return Error::success();
    395 }
    396 
    397 Error InstructionBenchmark::writeYaml(const LLVMState &State,
    398                                       const StringRef Filename) {
    399   if (Filename == "-") {
    400     if (auto Err = writeYamlTo(State, outs()))
    401       return Err;
    402   } else {
    403     int ResultFD = 0;
    404     if (auto E = errorCodeToError(openFileForWrite(Filename, ResultFD,
    405                                                    sys::fs::CD_CreateAlways,
    406                                                    sys::fs::OF_TextWithCRLF))) {
    407       return E;
    408     }
    409     raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/);
    410     if (auto Err = writeYamlTo(State, Ostr))
    411       return Err;
    412   }
    413   return Error::success();
    414 }
    415 
    416 void PerInstructionStats::push(const BenchmarkMeasure &BM) {
    417   if (Key.empty())
    418     Key = BM.Key;
    419   assert(Key == BM.Key);
    420   ++NumValues;
    421   SumValues += BM.PerInstructionValue;
    422   MaxValue = std::max(MaxValue, BM.PerInstructionValue);
    423   MinValue = std::min(MinValue, BM.PerInstructionValue);
    424 }
    425 
    426 } // namespace exegesis
    427 } // namespace llvm
    428