|  | //===-- 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 "BenchmarkRunner.h" | 
|  | #include "llvm/ADT/STLExtras.h" | 
|  | #include "llvm/ADT/bit.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 kIntegerPrefix[] = "i_0x"; | 
|  | static constexpr const char kDoublePrefix[] = "f_"; | 
|  | static constexpr const char kInvalidOperand[] = "INVALID"; | 
|  |  | 
|  | namespace llvm { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // A mutable struct holding an LLVMState that can be passed through the | 
|  | // serialization process to encode/decode registers and instructions. | 
|  | struct YamlContext { | 
|  | YamlContext(const exegesis::LLVMState &State) | 
|  | : State(&State), ErrorStream(LastError) {} | 
|  |  | 
|  | void serializeMCInst(const llvm::MCInst &MCInst, llvm::raw_ostream &OS) { | 
|  | OS << getInstrName(MCInst.getOpcode()); | 
|  | for (const auto &Op : MCInst) { | 
|  | OS << ' '; | 
|  | serializeMCOperand(Op, OS); | 
|  | } | 
|  | } | 
|  |  | 
|  | void deserializeMCInst(llvm::StringRef String, llvm::MCInst &Value) { | 
|  | llvm::SmallVector<llvm::StringRef, 8> Pieces; | 
|  | String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false); | 
|  | if (Pieces.empty()) { | 
|  | ErrorStream << "Unknown Instruction: '" << String << "'"; | 
|  | return; | 
|  | } | 
|  | bool ProcessOpcode = true; | 
|  | for (llvm::StringRef Piece : Pieces) { | 
|  | if (ProcessOpcode) | 
|  | Value.setOpcode(getInstrOpcode(Piece)); | 
|  | else | 
|  | Value.addOperand(deserializeMCOperand(Piece)); | 
|  | ProcessOpcode = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::string &getLastError() { return ErrorStream.str(); } | 
|  |  | 
|  | llvm::raw_string_ostream &getErrorStream() { return ErrorStream; } | 
|  |  | 
|  | llvm::StringRef getRegName(unsigned RegNo) { | 
|  | const llvm::StringRef RegName = State->getRegInfo().getName(RegNo); | 
|  | if (RegName.empty()) | 
|  | ErrorStream << "No register with enum value" << RegNo; | 
|  | return RegName; | 
|  | } | 
|  |  | 
|  | unsigned getRegNo(llvm::StringRef RegName) { | 
|  | const llvm::MCRegisterInfo &RegInfo = State->getRegInfo(); | 
|  | for (unsigned E = RegInfo.getNumRegs(), I = 0; I < E; ++I) | 
|  | if (RegInfo.getName(I) == RegName) | 
|  | return I; | 
|  | ErrorStream << "No register with name " << RegName; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | private: | 
|  | void serializeIntegerOperand(llvm::raw_ostream &OS, int64_t Value) { | 
|  | OS << kIntegerPrefix; | 
|  | OS.write_hex(llvm::bit_cast<uint64_t>(Value)); | 
|  | } | 
|  |  | 
|  | bool tryDeserializeIntegerOperand(llvm::StringRef String, int64_t &Value) { | 
|  | if (!String.consume_front(kIntegerPrefix)) | 
|  | return false; | 
|  | return !String.consumeInteger(16, Value); | 
|  | } | 
|  |  | 
|  | void serializeFPOperand(llvm::raw_ostream &OS, double Value) { | 
|  | OS << kDoublePrefix << llvm::format("%la", Value); | 
|  | } | 
|  |  | 
|  | bool tryDeserializeFPOperand(llvm::StringRef String, double &Value) { | 
|  | if (!String.consume_front(kDoublePrefix)) | 
|  | return false; | 
|  | char *EndPointer = nullptr; | 
|  | Value = strtod(String.begin(), &EndPointer); | 
|  | return EndPointer == String.end(); | 
|  | } | 
|  |  | 
|  | void serializeMCOperand(const llvm::MCOperand &MCOperand, | 
|  | llvm::raw_ostream &OS) { | 
|  | if (MCOperand.isReg()) { | 
|  | OS << getRegName(MCOperand.getReg()); | 
|  | } else if (MCOperand.isImm()) { | 
|  | serializeIntegerOperand(OS, MCOperand.getImm()); | 
|  | } else if (MCOperand.isFPImm()) { | 
|  | serializeFPOperand(OS, MCOperand.getFPImm()); | 
|  | } else { | 
|  | OS << kInvalidOperand; | 
|  | } | 
|  | } | 
|  |  | 
|  | llvm::MCOperand deserializeMCOperand(llvm::StringRef String) { | 
|  | assert(!String.empty()); | 
|  | int64_t IntValue = 0; | 
|  | double DoubleValue = 0; | 
|  | if (tryDeserializeIntegerOperand(String, IntValue)) | 
|  | return llvm::MCOperand::createImm(IntValue); | 
|  | if (tryDeserializeFPOperand(String, DoubleValue)) | 
|  | return llvm::MCOperand::createFPImm(DoubleValue); | 
|  | if (unsigned RegNo = getRegNo(String)) | 
|  | return llvm::MCOperand::createReg(RegNo); | 
|  | if (String != kInvalidOperand) | 
|  | ErrorStream << "Unknown Operand: '" << String << "'"; | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | llvm::StringRef getInstrName(unsigned InstrNo) { | 
|  | const llvm::StringRef InstrName = State->getInstrInfo().getName(InstrNo); | 
|  | if (InstrName.empty()) | 
|  | ErrorStream << "No opcode with enum value" << InstrNo; | 
|  | return InstrName; | 
|  | } | 
|  |  | 
|  | unsigned getInstrOpcode(llvm::StringRef InstrName) { | 
|  | const llvm::MCInstrInfo &InstrInfo = State->getInstrInfo(); | 
|  | for (unsigned E = InstrInfo.getNumOpcodes(), I = 0; I < E; ++I) | 
|  | if (InstrInfo.getName(I) == InstrName) | 
|  | return I; | 
|  | ErrorStream << "No opcode with name " << InstrName; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | const llvm::exegesis::LLVMState *State; | 
|  | std::string LastError; | 
|  | llvm::raw_string_ostream ErrorStream; | 
|  | }; | 
|  | } // namespace | 
|  |  | 
|  | // Defining YAML traits for IO. | 
|  | namespace yaml { | 
|  |  | 
|  | static YamlContext &getTypedContext(void *Ctx) { | 
|  | return *reinterpret_cast<YamlContext *>(Ctx); | 
|  | } | 
|  |  | 
|  | // 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) { | 
|  | getTypedContext(Ctx).serializeMCInst(Value, Out); | 
|  | } | 
|  |  | 
|  | static StringRef input(StringRef Scalar, void *Ctx, llvm::MCInst &Value) { | 
|  | YamlContext &Context = getTypedContext(Ctx); | 
|  | Context.deserializeMCInst(Scalar, Value); | 
|  | return Context.getLastError(); | 
|  | } | 
|  |  | 
|  | // By default strings are quoted only when necessary. | 
|  | // We force the use of single quotes for uniformity. | 
|  | 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); | 
|  | if (!Io.outputting()) { | 
|  | // For backward compatibility, interpret debug_string as a key. | 
|  | Io.mapOptional("debug_string", Obj.Key); | 
|  | } | 
|  | Io.mapRequired("value", Obj.PerInstructionValue); | 
|  | Io.mapOptional("per_snippet_value", Obj.PerSnippetValue); | 
|  | } | 
|  | 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); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // std::vector<exegesis::RegisterValue> will be rendered as a list. | 
|  | template <> struct SequenceElementTraits<exegesis::RegisterValue> { | 
|  | static const bool flow = false; | 
|  | }; | 
|  |  | 
|  | template <> struct ScalarTraits<exegesis::RegisterValue> { | 
|  | static constexpr const unsigned kRadix = 16; | 
|  | static constexpr const bool kSigned = false; | 
|  |  | 
|  | static void output(const exegesis::RegisterValue &RV, void *Ctx, | 
|  | llvm::raw_ostream &Out) { | 
|  | YamlContext &Context = getTypedContext(Ctx); | 
|  | Out << Context.getRegName(RV.Register) << "=0x" | 
|  | << RV.Value.toString(kRadix, kSigned); | 
|  | } | 
|  |  | 
|  | static StringRef input(StringRef String, void *Ctx, | 
|  | exegesis::RegisterValue &RV) { | 
|  | llvm::SmallVector<llvm::StringRef, 2> Pieces; | 
|  | String.split(Pieces, "=0x", /* MaxSplit */ -1, | 
|  | /* KeepEmpty */ false); | 
|  | YamlContext &Context = getTypedContext(Ctx); | 
|  | if (Pieces.size() == 2) { | 
|  | RV.Register = Context.getRegNo(Pieces[0]); | 
|  | const unsigned BitsNeeded = llvm::APInt::getBitsNeeded(Pieces[1], kRadix); | 
|  | RV.Value = llvm::APInt(BitsNeeded, Pieces[1], kRadix); | 
|  | } else { | 
|  | Context.getErrorStream() | 
|  | << "Unknown initial register value: '" << String << "'"; | 
|  | } | 
|  | return Context.getLastError(); | 
|  | } | 
|  |  | 
|  | static QuotingType mustQuote(StringRef) { return QuotingType::Single; } | 
|  |  | 
|  | static const bool flow = true; | 
|  | }; | 
|  |  | 
|  | template <> | 
|  | struct MappingContextTraits<exegesis::InstructionBenchmarkKey, YamlContext> { | 
|  | static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj, | 
|  | YamlContext &Context) { | 
|  | Io.setContext(&Context); | 
|  | Io.mapRequired("instructions", Obj.Instructions); | 
|  | Io.mapOptional("config", Obj.Config); | 
|  | Io.mapRequired("register_initial_values", Obj.RegisterInitialValues); | 
|  | } | 
|  | }; | 
|  |  | 
|  | template <> | 
|  | struct MappingContextTraits<exegesis::InstructionBenchmark, YamlContext> { | 
|  | struct NormalizedBinary { | 
|  | 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, | 
|  | YamlContext &Context) { | 
|  | Io.mapRequired("mode", Obj.Mode); | 
|  | Io.mapRequired("key", Obj.Key, Context); | 
|  | 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 exegesis { | 
|  |  | 
|  | llvm::Expected<InstructionBenchmark> | 
|  | InstructionBenchmark::readYaml(const LLVMState &State, | 
|  | llvm::StringRef Filename) { | 
|  | if (auto ExpectedMemoryBuffer = | 
|  | llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) { | 
|  | llvm::yaml::Input Yin(*ExpectedMemoryBuffer.get()); | 
|  | YamlContext Context(State); | 
|  | InstructionBenchmark Benchmark; | 
|  | if (Yin.setCurrentDocument()) | 
|  | llvm::yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context); | 
|  | if (!Context.getLastError().empty()) | 
|  | return llvm::make_error<BenchmarkFailure>(Context.getLastError()); | 
|  | return Benchmark; | 
|  | } else { | 
|  | return ExpectedMemoryBuffer.takeError(); | 
|  | } | 
|  | } | 
|  |  | 
|  | llvm::Expected<std::vector<InstructionBenchmark>> | 
|  | InstructionBenchmark::readYamls(const LLVMState &State, | 
|  | llvm::StringRef Filename) { | 
|  | if (auto ExpectedMemoryBuffer = | 
|  | llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) { | 
|  | llvm::yaml::Input Yin(*ExpectedMemoryBuffer.get()); | 
|  | YamlContext Context(State); | 
|  | std::vector<InstructionBenchmark> Benchmarks; | 
|  | while (Yin.setCurrentDocument()) { | 
|  | Benchmarks.emplace_back(); | 
|  | yamlize(Yin, Benchmarks.back(), /*unused*/ true, Context); | 
|  | if (Yin.error()) | 
|  | return llvm::errorCodeToError(Yin.error()); | 
|  | if (!Context.getLastError().empty()) | 
|  | return llvm::make_error<BenchmarkFailure>(Context.getLastError()); | 
|  | Yin.nextDocument(); | 
|  | } | 
|  | return Benchmarks; | 
|  | } else { | 
|  | return ExpectedMemoryBuffer.takeError(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void InstructionBenchmark::writeYamlTo(const LLVMState &State, | 
|  | llvm::raw_ostream &OS) { | 
|  | llvm::yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/); | 
|  | YamlContext Context(State); | 
|  | Yout.beginDocuments(); | 
|  | llvm::yaml::yamlize(Yout, *this, /*unused*/ true, Context); | 
|  | Yout.endDocuments(); | 
|  | } | 
|  |  | 
|  | void InstructionBenchmark::readYamlFrom(const LLVMState &State, | 
|  | llvm::StringRef InputContent) { | 
|  | llvm::yaml::Input Yin(InputContent); | 
|  | YamlContext Context(State); | 
|  | if (Yin.setCurrentDocument()) | 
|  | llvm::yaml::yamlize(Yin, *this, /*unused*/ true, Context); | 
|  | } | 
|  |  | 
|  | llvm::Error InstructionBenchmark::writeYaml(const LLVMState &State, | 
|  | const llvm::StringRef Filename) { | 
|  | if (Filename == "-") { | 
|  | writeYamlTo(State, 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(State, Ostr); | 
|  | } | 
|  | return llvm::Error::success(); | 
|  | } | 
|  |  | 
|  | void PerInstructionStats::push(const BenchmarkMeasure &BM) { | 
|  | if (Key.empty()) | 
|  | Key = BM.Key; | 
|  | assert(Key == BM.Key); | 
|  | ++NumValues; | 
|  | SumValues += BM.PerInstructionValue; | 
|  | MaxValue = std::max(MaxValue, BM.PerInstructionValue); | 
|  | MinValue = std::min(MinValue, BM.PerInstructionValue); | 
|  | } | 
|  |  | 
|  | } // namespace exegesis | 
|  | } // namespace llvm |