Index: tools/llvm-exegesis/lib/BenchmarkResult.h =================================================================== --- tools/llvm-exegesis/lib/BenchmarkResult.h +++ tools/llvm-exegesis/lib/BenchmarkResult.h @@ -16,17 +16,24 @@ #ifndef LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRESULT_H #define LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRESULT_H +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/MC/MCInst.h" +#include "llvm/MC/MCInstBuilder.h" #include "llvm/Support/YAMLTraits.h" #include #include +#include #include namespace exegesis { +struct BenchmarkResultContext; // Forward declaration. + struct InstructionBenchmarkKey { // The LLVM opcode name. - std::string OpcodeName; + std::string OpcodeName; // FIXME: Deprecated, use Instructions below. + std::vector Instructions; enum ModeE { Unknown, Latency, Uops }; ModeE Mode; // An opaque configuration, that can be used to separate several benchmarks of @@ -50,16 +57,23 @@ std::string Error; std::string Info; - static InstructionBenchmark readYamlOrDie(llvm::StringRef Filename); + // Read functions. + static InstructionBenchmark + readYamlOrDie(const BenchmarkResultContext &Context, + llvm::StringRef Filename); + static std::vector + readYamlsOrDie(const BenchmarkResultContext &Context, + llvm::StringRef Filename); - // Read functions. - readYamlsOrDie(llvm::StringRef Filename); - void readYamlFrom(llvm::StringRef InputContent); + void readYamlFrom(const BenchmarkResultContext &Context, + llvm::StringRef InputContent); // Write functions, non-const because of YAML traits. - void writeYamlTo(llvm::raw_ostream &S); - void writeYamlOrDie(const llvm::StringRef Filename); + void writeYamlTo(const BenchmarkResultContext &Context, llvm::raw_ostream &S); + + void writeYamlOrDie(const BenchmarkResultContext &Context, + const llvm::StringRef Filename); }; //------------------------------------------------------------------------------ @@ -87,6 +101,38 @@ double MinValue = std::numeric_limits::max(); }; +// This context is used when de/serializing InstructionBenchmark to guarantee +// that Registers and Instructions are human readable and preserved accross +// different versions of LLVM. +struct BenchmarkResultContext { + BenchmarkResultContext() = default; + BenchmarkResultContext(BenchmarkResultContext &&) = default; + BenchmarkResultContext &operator=(BenchmarkResultContext &&) = default; + BenchmarkResultContext(const BenchmarkResultContext &) = delete; + BenchmarkResultContext &operator=(const BenchmarkResultContext &) = delete; + + // Populate Registers and Instruction mapping. + void addRegEntry(unsigned RegNo, llvm::StringRef Name); + void addInstrEntry(unsigned Opcode, llvm::StringRef Name); + + // Register accessors. + llvm::StringRef getRegName(unsigned RegNo) const; + unsigned getRegNo(llvm::StringRef Name) const; // 0 is not found. + + // Instruction accessors. + llvm::StringRef getInstrName(unsigned Opcode) const; + unsigned getInstrOpcode(llvm::StringRef Name) const; // 0 is not found. + +private: + // Ideally we would like to use MCRegisterInfo and MCInstrInfo but doing so + // would make testing harder, instead we create a mapping that we can easily + // populate. + std::unordered_map InstrOpcodeToName; + std::unordered_map RegNoToName; + llvm::StringMap InstrNameToOpcode; + llvm::StringMap RegNameToNo; +}; + } // namespace exegesis #endif // LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRESULT_H Index: tools/llvm-exegesis/lib/BenchmarkResult.cpp =================================================================== --- tools/llvm-exegesis/lib/BenchmarkResult.cpp +++ tools/llvm-exegesis/lib/BenchmarkResult.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "BenchmarkResult.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/FileSystem.h" @@ -18,6 +19,38 @@ namespace llvm { namespace yaml { +// std::vector will be rendered as a list. +template <> struct SequenceElementTraits { + static const bool flow = false; +}; + +template <> struct ScalarTraits { + + static void output(const llvm::MCInst &Value, void *Ctx, + llvm::raw_ostream &Out) { + assert(Ctx); + auto *Context = static_cast(Ctx); + const StringRef Name = Context->getInstrName(Value.getOpcode()); + assert(!Name.empty()); + Out << Name; + } + + static StringRef input(StringRef Scalar, void *Ctx, llvm::MCInst &Value) { + assert(Ctx); + auto *Context = static_cast(Ctx); + const unsigned Opcode = Context->getInstrOpcode(Scalar); + if (Opcode == 0) { + return "Unable to parse instruction"; + } + Value.setOpcode(Opcode); + return StringRef(); + } + + static QuotingType mustQuote(StringRef) { return QuotingType::Single; } + + static const bool flow = true; +}; + // std::vector will be rendered as a list. template <> struct SequenceElementTraits { static const bool flow = false; @@ -47,6 +80,7 @@ template <> struct MappingTraits { static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj) { Io.mapRequired("opcode_name", Obj.OpcodeName); + Io.mapRequired("instructions", Obj.Instructions); Io.mapRequired("mode", Obj.Mode); Io.mapOptional("config", Obj.Config); } @@ -71,46 +105,104 @@ 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 -static ObjectOrList readYamlOrDieCommon(llvm::StringRef Filename) { +static ObjectOrList readYamlOrDieCommon(const BenchmarkResultContext &Context, + llvm::StringRef Filename) { std::unique_ptr MemBuffer = llvm::cantFail( llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))); - llvm::yaml::Input Yin(*MemBuffer); + // YAML IO requires a mutable pointer to Context but we guarantee to not + // modify it. + llvm::yaml::Input Yin(*MemBuffer, + const_cast(&Context)); ObjectOrList Benchmark; Yin >> Benchmark; return Benchmark; } InstructionBenchmark -InstructionBenchmark::readYamlOrDie(llvm::StringRef Filename) { - return readYamlOrDieCommon(Filename); +InstructionBenchmark::readYamlOrDie(const BenchmarkResultContext &Context, + llvm::StringRef Filename) { + return readYamlOrDieCommon(Context, Filename); } std::vector -InstructionBenchmark::readYamlsOrDie(llvm::StringRef Filename) { - return readYamlOrDieCommon>(Filename); +InstructionBenchmark::readYamlsOrDie(const BenchmarkResultContext &Context, + llvm::StringRef Filename) { + return readYamlOrDieCommon>(Context, + Filename); } -void InstructionBenchmark::writeYamlTo(llvm::raw_ostream &S) { - llvm::yaml::Output Yout(S); +void InstructionBenchmark::writeYamlTo(const BenchmarkResultContext &Context, + llvm::raw_ostream &S) { + // YAML IO requires a mutable pointer to Context but we guarantee to not + // modify it. + llvm::yaml::Output Yout(S, const_cast(&Context)); Yout << *this; } -void InstructionBenchmark::readYamlFrom(llvm::StringRef InputContent) { - llvm::yaml::Input Yin(InputContent); +void InstructionBenchmark::readYamlFrom(const BenchmarkResultContext &Context, + llvm::StringRef InputContent) { + // YAML IO requires a mutable pointer to Context but we guarantee to not + // modify it. + llvm::yaml::Input Yin(InputContent, + const_cast(&Context)); Yin >> *this; } // FIXME: Change the API to let the caller handle errors. -void InstructionBenchmark::writeYamlOrDie(const llvm::StringRef Filename) { +void InstructionBenchmark::writeYamlOrDie(const BenchmarkResultContext &Context, + const llvm::StringRef Filename) { if (Filename == "-") { - writeYamlTo(llvm::outs()); + writeYamlTo(Context, llvm::outs()); } else { int ResultFD = 0; llvm::cantFail(llvm::errorCodeToError( openFileForWrite(Filename, ResultFD, llvm::sys::fs::F_Text))); llvm::raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/); - writeYamlTo(Ostr); + writeYamlTo(Context, Ostr); } } Index: tools/llvm-exegesis/llvm-exegesis.cpp =================================================================== --- tools/llvm-exegesis/llvm-exegesis.cpp +++ tools/llvm-exegesis/llvm-exegesis.cpp @@ -92,6 +92,21 @@ llvm::report_fatal_error(llvm::Twine("unknown opcode ").concat(OpcodeName)); } +static BenchmarkResultContext +getBenchmarkResultContext(const LLVMState &State) { + BenchmarkResultContext Ctx; + + const llvm::MCInstrInfo &InstrInfo = State.getInstrInfo(); + for (unsigned E = InstrInfo.getNumOpcodes(), I = 0; I < E; ++I) + Ctx.addInstrEntry(I, InstrInfo.getName(I).data()); + + const llvm::MCRegisterInfo &RegInfo = State.getRegInfo(); + for (unsigned E = RegInfo.getNumRegs(), I = 0; I < E; ++I) + Ctx.addRegEntry(I, RegInfo.getName(I)); + + return Ctx; +} + void benchmarkMain() { if (exegesis::pfm::pfmInitialize()) llvm::report_fatal_error("cannot initialize libpfm"); @@ -124,7 +139,7 @@ llvm::report_fatal_error("--num-repetitions must be greater than zero"); Runner->run(GetOpcodeOrDie(State.getInstrInfo()), Filter, NumRepetitions) - .writeYamlOrDie(BenchmarkFile); + .writeYamlOrDie(getBenchmarkResultContext(State), BenchmarkFile); exegesis::pfm::pfmTerminate(); } @@ -132,7 +147,7 @@ // if OutputFilename is non-empty. template static void maybeRunAnalysis(const Analysis &Analyzer, const std::string &Name, - const std::string &OutputFilename) { + const std::string &OutputFilename) { if (OutputFilename.empty()) return; if (OutputFilename != "-") { @@ -149,9 +164,14 @@ } static void analysisMain() { + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + // Read benchmarks. + const LLVMState State; const std::vector Points = - InstructionBenchmark::readYamlsOrDie(BenchmarkFile); + InstructionBenchmark::readYamlsOrDie(getBenchmarkResultContext(State), + BenchmarkFile); llvm::outs() << "Parsed " << Points.size() << " benchmark points\n"; if (Points.empty()) { llvm::errs() << "no benchmarks to analyze\n"; @@ -160,9 +180,6 @@ // FIXME: Check that all points have the same triple/cpu. // FIXME: Merge points from several runs (latency and uops). - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - std::string Error; const auto *TheTarget = llvm::TargetRegistry::lookupTarget(Points[0].LLVMTriple, Error); Index: unittests/tools/llvm-exegesis/BenchmarkResultTest.cpp =================================================================== --- unittests/tools/llvm-exegesis/BenchmarkResultTest.cpp +++ unittests/tools/llvm-exegesis/BenchmarkResultTest.cpp @@ -16,18 +16,35 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using ::testing::AllOf; +using ::testing::Eq; +using ::testing::get; +using ::testing::Pointwise; +using ::testing::Property; + namespace exegesis { bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) { return std::tie(A.Key, A.Value) == std::tie(B.Key, B.Value); } +MATCHER(EqMCInst, "") { + return get<0>(arg).getOpcode() == get<1>(arg).getOpcode(); +} + namespace { +static constexpr const unsigned kInstrId = 5; +static constexpr const char kInstrName[] = "Instruction5"; + TEST(BenchmarkResultTest, WriteToAndReadFromDisk) { + BenchmarkResultContext Ctx; + Ctx.addInstrEntry(kInstrId, kInstrName); + InstructionBenchmark ToDisk; ToDisk.Key.OpcodeName = "name"; + ToDisk.Key.Instructions.push_back(llvm::MCInstBuilder(kInstrId)); ToDisk.Key.Mode = InstructionBenchmarkKey::Latency; ToDisk.Key.Config = "config"; ToDisk.CpuName = "cpu_name"; @@ -43,14 +60,15 @@ EC = llvm::sys::fs::createUniqueDirectory("BenchmarkResultTestDir", Filename); ASSERT_FALSE(EC); llvm::sys::path::append(Filename, "data.yaml"); - - ToDisk.writeYamlOrDie(Filename); + ToDisk.writeYamlOrDie(Ctx, Filename); { // One-element version. - const auto FromDisk = InstructionBenchmark::readYamlOrDie(Filename); + const auto FromDisk = InstructionBenchmark::readYamlOrDie(Ctx, Filename); EXPECT_EQ(FromDisk.Key.OpcodeName, ToDisk.Key.OpcodeName); + EXPECT_THAT(FromDisk.Key.Instructions, + Pointwise(EqMCInst(), ToDisk.Key.Instructions)); EXPECT_EQ(FromDisk.Key.Mode, ToDisk.Key.Mode); EXPECT_EQ(FromDisk.Key.Config, ToDisk.Key.Config); EXPECT_EQ(FromDisk.CpuName, ToDisk.CpuName); @@ -62,10 +80,13 @@ } { // Vector version. - const auto FromDiskVector = InstructionBenchmark::readYamlsOrDie(Filename); + const auto FromDiskVector = + InstructionBenchmark::readYamlsOrDie(Ctx, Filename); ASSERT_EQ(FromDiskVector.size(), size_t{1}); const auto FromDisk = FromDiskVector[0]; EXPECT_EQ(FromDisk.Key.OpcodeName, ToDisk.Key.OpcodeName); + EXPECT_THAT(FromDisk.Key.Instructions, + Pointwise(EqMCInst(), ToDisk.Key.Instructions)); EXPECT_EQ(FromDisk.Key.Mode, ToDisk.Key.Mode); EXPECT_EQ(FromDisk.Key.Config, ToDisk.Key.Config); EXPECT_EQ(FromDisk.CpuName, ToDisk.CpuName);