OSDN Git Service

[llvm-exegesis] Add instructions to BenchmarkResult Key.
authorClement Courbet <courbet@google.com>
Tue, 5 Jun 2018 10:56:19 +0000 (10:56 +0000)
committerClement Courbet <courbet@google.com>
Tue, 5 Jun 2018 10:56:19 +0000 (10:56 +0000)
We want llvm-exegesis to explore instructions (effect of initial register values, effect of operand selection). To enable this a BenchmarkResult muststore all the relevant data in its key. This patch starts adding such data. Here we simply allow to store the generated instructions, following patches will add operands and initial values for registers.

https://reviews.llvm.org/D47764

Authored by: Guilluame Chatelet

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@334008 91177308-0d34-0410-b5e6-96231b3b80d8

tools/llvm-exegesis/lib/BenchmarkResult.cpp
tools/llvm-exegesis/lib/BenchmarkResult.h
tools/llvm-exegesis/llvm-exegesis.cpp
unittests/tools/llvm-exegesis/BenchmarkResultTest.cpp

index fd5b350..441aee0 100644 (file)
@@ -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"
 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) {
+    assert(Ctx);
+    auto *Context = static_cast<const exegesis::BenchmarkResultContext *>(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<const exegesis::BenchmarkResultContext *>(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<exegesis::Measure> will be rendered as a list.
 template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {
   static const bool flow = false;
@@ -47,6 +80,7 @@ struct ScalarEnumerationTraits<exegesis::InstructionBenchmarkKey::ModeE> {
 template <> struct MappingTraits<exegesis::InstructionBenchmarkKey> {
   static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj) {
     Io.mapRequired("opcode_name", Obj.OpcodeName);
+    Io.mapOptional("instructions", Obj.Instructions);
     Io.mapRequired("mode", Obj.Mode);
     Io.mapOptional("config", Obj.Config);
   }
@@ -71,46 +105,104 @@ 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 ObjectOrList readYamlOrDieCommon(llvm::StringRef Filename) {
+static ObjectOrList readYamlOrDieCommon(const BenchmarkResultContext &Context,
+                                        llvm::StringRef Filename) {
   std::unique_ptr<llvm::MemoryBuffer> 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<BenchmarkResultContext *>(&Context));
   ObjectOrList Benchmark;
   Yin >> Benchmark;
   return Benchmark;
 }
 
 InstructionBenchmark
-InstructionBenchmark::readYamlOrDie(llvm::StringRef Filename) {
-  return readYamlOrDieCommon<InstructionBenchmark>(Filename);
+InstructionBenchmark::readYamlOrDie(const BenchmarkResultContext &Context,
+                                    llvm::StringRef Filename) {
+  return readYamlOrDieCommon<InstructionBenchmark>(Context, Filename);
 }
 
 std::vector<InstructionBenchmark>
-InstructionBenchmark::readYamlsOrDie(llvm::StringRef Filename) {
-  return readYamlOrDieCommon<std::vector<InstructionBenchmark>>(Filename);
+InstructionBenchmark::readYamlsOrDie(const BenchmarkResultContext &Context,
+                                     llvm::StringRef Filename) {
+  return readYamlOrDieCommon<std::vector<InstructionBenchmark>>(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<BenchmarkResultContext *>(&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<BenchmarkResultContext *>(&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 b160507..9b87482 100644 (file)
 #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 <limits>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 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<llvm::MCInst> Instructions;
   enum ModeE { Unknown, Latency, Uops };
   ModeE Mode;
   // An opaque configuration, that can be used to separate several benchmarks of
@@ -50,16 +57,23 @@ struct InstructionBenchmark {
   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<InstructionBenchmark>
+  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 @@ private:
   double MinValue = std::numeric_limits<double>::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<unsigned, llvm::StringRef> InstrOpcodeToName;
+  std::unordered_map<unsigned, llvm::StringRef> RegNoToName;
+  llvm::StringMap<unsigned> InstrNameToOpcode;
+  llvm::StringMap<unsigned> RegNameToNo;
+};
+
 } // namespace exegesis
 
 #endif // LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRESULT_H
index 3c57706..a1848c3 100644 (file)
@@ -92,6 +92,21 @@ static unsigned GetOpcodeOrDie(const llvm::MCInstrInfo &MCInstrInfo) {
   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 @@ void benchmarkMain() {
     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 @@ void benchmarkMain() {
 // if OutputFilename is non-empty.
 template <typename Pass>
 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 maybeRunAnalysis(const Analysis &Analyzer, const std::string &Name,
 }
 
 static void analysisMain() {
+  llvm::InitializeNativeTarget();
+  llvm::InitializeNativeTargetAsmPrinter();
+
   // Read benchmarks.
+  const LLVMState State;
   const std::vector<InstructionBenchmark> 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 @@ static void analysisMain() {
   // 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 d3e3a5f..2c12c82 100644 (file)
 #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 @@ TEST(BenchmarkResultTest, WriteToAndReadFromDisk) {
   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 @@ TEST(BenchmarkResultTest, WriteToAndReadFromDisk) {
   }
   {
     // 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);