Index: tools/llvm-exegesis/lib/BenchmarkResult.h =================================================================== --- tools/llvm-exegesis/lib/BenchmarkResult.h +++ tools/llvm-exegesis/lib/BenchmarkResult.h @@ -16,6 +16,7 @@ #ifndef LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRESULT_H #define LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRESULT_H +#include "LlvmState.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/MC/MCInst.h" @@ -28,8 +29,6 @@ namespace exegesis { -struct BenchmarkResultContext; // Forward declaration. - struct InstructionBenchmarkKey { // The LLVM opcode name. std::vector Instructions; @@ -62,19 +61,17 @@ // Read functions. static llvm::Expected - readYaml(const BenchmarkResultContext &Context, llvm::StringRef Filename); + readYaml(const LLVMState &State, llvm::StringRef Filename); static llvm::Expected> - readYamls(const BenchmarkResultContext &Context, llvm::StringRef Filename); + readYamls(const LLVMState &State, llvm::StringRef Filename); - void readYamlFrom(const BenchmarkResultContext &Context, - llvm::StringRef InputContent); + void readYamlFrom(const LLVMState &State, llvm::StringRef InputContent); // Write functions, non-const because of YAML traits. - void writeYamlTo(const BenchmarkResultContext &Context, llvm::raw_ostream &S); + void writeYamlTo(const LLVMState &State, llvm::raw_ostream &S); - llvm::Error writeYaml(const BenchmarkResultContext &Context, - const llvm::StringRef Filename); + llvm::Error writeYaml(const LLVMState &State, const llvm::StringRef Filename); }; //------------------------------------------------------------------------------ @@ -102,38 +99,6 @@ 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 "BenchmarkRunner.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ObjectYAML/YAML.h" @@ -18,75 +19,106 @@ static constexpr const char kIntegerFormat[] = "i_0x%" PRId64 "x"; static constexpr const char kDoubleFormat[] = "f_%la"; - -static void serialize(const exegesis::BenchmarkResultContext &Context, - const llvm::MCOperand &MCOperand, llvm::raw_ostream &OS) { - if (MCOperand.isReg()) { - OS << Context.getRegName(MCOperand.getReg()); - } else if (MCOperand.isImm()) { - OS << llvm::format(kIntegerFormat, MCOperand.getImm()); - } else if (MCOperand.isFPImm()) { - OS << llvm::format(kDoubleFormat, MCOperand.getFPImm()); - } else { - OS << "INVALID"; +static constexpr const char kInvalidOperand[] = "INVALID"; + +// 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); + } } -} -static void serialize(const exegesis::BenchmarkResultContext &Context, - const llvm::MCInst &MCInst, llvm::raw_ostream &OS) { - OS << Context.getInstrName(MCInst.getOpcode()); - for (const auto &Op : MCInst) { - OS << ' '; - serialize(Context, Op, OS); + void deserializeMCInst(llvm::StringRef String, llvm::MCInst &Value) { + llvm::SmallVector 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; + } } -} - -static llvm::MCOperand -deserialize(const exegesis::BenchmarkResultContext &Context, - llvm::StringRef String) { - assert(!String.empty()); - int64_t IntValue = 0; - double DoubleValue = 0; - if (sscanf(String.data(), kIntegerFormat, &IntValue) == 1) - return llvm::MCOperand::createImm(IntValue); - if (sscanf(String.data(), kDoubleFormat, &DoubleValue) == 1) - return llvm::MCOperand::createFPImm(DoubleValue); - if (unsigned RegNo = Context.getRegNo(String)) // Returns 0 if invalid. - return llvm::MCOperand::createReg(RegNo); - return {}; -} -static llvm::StringRef -deserialize(const exegesis::BenchmarkResultContext &Context, - llvm::StringRef String, llvm::MCInst &Value) { - llvm::SmallVector Pieces; - String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false); - if (Pieces.empty()) - return "Invalid Instruction"; - bool ProcessOpcode = true; - for (llvm::StringRef Piece : Pieces) { - if (ProcessOpcode) { - ProcessOpcode = false; - Value.setOpcode(Context.getInstrOpcode(Piece)); - if (Value.getOpcode() == 0) - return "Unknown Opcode Name"; + std::string &getLastError() { return ErrorStream.str(); } + +private: + void serializeMCOperand(const llvm::MCOperand &MCOperand, + llvm::raw_ostream &OS) { + if (MCOperand.isReg()) { + OS << getRegName(MCOperand.getReg()); + } else if (MCOperand.isImm()) { + OS << llvm::format(kIntegerFormat, MCOperand.getImm()); + } else if (MCOperand.isFPImm()) { + OS << llvm::format(kDoubleFormat, MCOperand.getFPImm()); } else { - Value.addOperand(deserialize(Context, Piece)); + OS << kInvalidOperand; } } - return {}; -} -// YAML IO requires a mutable pointer to Context but we guarantee to not -// modify it. -static void *getUntypedContext(const exegesis::BenchmarkResultContext &Ctx) { - return const_cast(&Ctx); -} + llvm::MCOperand deserializeMCOperand(llvm::StringRef String) { + assert(!String.empty()); + int64_t IntValue = 0; + double DoubleValue = 0; + if (sscanf(String.data(), kIntegerFormat, &IntValue) == 1) + return llvm::MCOperand::createImm(IntValue); + if (sscanf(String.data(), kDoubleFormat, &DoubleValue) == 1) + return llvm::MCOperand::createFPImm(DoubleValue); + if (unsigned RegNo = getRegNo(String)) + return llvm::MCOperand::createReg(RegNo); + if (String != kInvalidOperand) + ErrorStream << "Unknown Operand: '" << String << "'"; + return {}; + } -static const exegesis::BenchmarkResultContext &getTypedContext(void *Ctx) { - assert(Ctx); - return *static_cast(Ctx); -} + 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; + } + + 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 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; + } + + 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 exegesis::LLVMState *State; + std::string LastError; + llvm::raw_string_ostream ErrorStream; +}; // Defining YAML traits for IO. namespace llvm { @@ -101,11 +133,13 @@ static void output(const llvm::MCInst &Value, void *Ctx, llvm::raw_ostream &Out) { - serialize(getTypedContext(Ctx), Value, Out); + reinterpret_cast(Ctx)->serializeMCInst(Value, Out); } static StringRef input(StringRef Scalar, void *Ctx, llvm::MCInst &Value) { - return deserialize(getTypedContext(Ctx), Scalar, Value); + YamlContext &Context = *reinterpret_cast(Ctx); + Context.deserializeMCInst(Scalar, Value); + return Context.getLastError(); } static QuotingType mustQuote(StringRef) { return QuotingType::Single; } @@ -139,14 +173,18 @@ } }; -template <> struct MappingTraits { - static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj) { +template <> +struct MappingContextTraits { + static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj, + YamlContext &Context) { + Io.setContext(&Context); Io.mapRequired("instructions", Obj.Instructions); Io.mapOptional("config", Obj.Config); } }; -template <> struct MappingTraits { +template <> +struct MappingContextTraits { class NormalizedBinary { public: NormalizedBinary(IO &io) {} @@ -164,9 +202,10 @@ BinaryRef Binary; }; - static void mapping(IO &Io, exegesis::InstructionBenchmark &Obj) { + static void mapping(IO &Io, exegesis::InstructionBenchmark &Obj, + YamlContext &Context) { Io.mapRequired("mode", Obj.Mode); - Io.mapRequired("key", Obj.Key); + Io.mapRequired("key", Obj.Key, Context); Io.mapRequired("cpu_name", Obj.CpuName); Io.mapRequired("llvm_triple", Obj.LLVMTriple); Io.mapRequired("num_repetitions", Obj.NumRepetitions); @@ -183,99 +222,68 @@ } // namespace yaml } // namespace llvm -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 -static llvm::Expected -readYamlCommon(const BenchmarkResultContext &Context, - llvm::StringRef Filename) { +llvm::Expected +InstructionBenchmark::readYaml(const LLVMState &State, + llvm::StringRef Filename) { if (auto ExpectedMemoryBuffer = llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) { - std::unique_ptr MemoryBuffer = - std::move(ExpectedMemoryBuffer.get()); - llvm::yaml::Input Yin(*MemoryBuffer, getUntypedContext(Context)); - ObjectOrList Benchmark; - Yin >> Benchmark; + 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(Context.getLastError()); return Benchmark; } else { return ExpectedMemoryBuffer.takeError(); } } -llvm::Expected -InstructionBenchmark::readYaml(const BenchmarkResultContext &Context, - llvm::StringRef Filename) { - return readYamlCommon(Context, Filename); -} - llvm::Expected> -InstructionBenchmark::readYamls(const BenchmarkResultContext &Context, +InstructionBenchmark::readYamls(const LLVMState &State, llvm::StringRef Filename) { - return readYamlCommon>(Context, Filename); + if (auto ExpectedMemoryBuffer = + llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) { + llvm::yaml::Input Yin(*ExpectedMemoryBuffer.get()); + YamlContext Context(State); + std::vector 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(Context.getLastError()); + Yin.nextDocument(); + } + return Benchmarks; + } else { + return ExpectedMemoryBuffer.takeError(); + } } -void InstructionBenchmark::writeYamlTo(const BenchmarkResultContext &Context, +void InstructionBenchmark::writeYamlTo(const LLVMState &State, llvm::raw_ostream &OS) { - llvm::yaml::Output Yout(OS, getUntypedContext(Context)); - Yout << *this; + llvm::yaml::Output Yout(OS); + YamlContext Context(State); + llvm::yaml::yamlize(Yout, *this, /*unused*/ true, Context); } -void InstructionBenchmark::readYamlFrom(const BenchmarkResultContext &Context, +void InstructionBenchmark::readYamlFrom(const LLVMState &State, llvm::StringRef InputContent) { - llvm::yaml::Input Yin(InputContent, getUntypedContext(Context)); - Yin >> *this; + llvm::yaml::Input Yin(InputContent); + YamlContext Context(State); + if (Yin.setCurrentDocument()) + llvm::yaml::yamlize(Yin, *this, /*unused*/ true, Context); } -llvm::Error -InstructionBenchmark::writeYaml(const BenchmarkResultContext &Context, - const llvm::StringRef Filename) { +llvm::Error InstructionBenchmark::writeYaml(const LLVMState &State, + const llvm::StringRef Filename) { if (Filename == "-") { - writeYamlTo(Context, llvm::outs()); + writeYamlTo(State, llvm::outs()); } else { int ResultFD = 0; if (auto E = llvm::errorCodeToError( @@ -284,7 +292,7 @@ return E; } llvm::raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/); - writeYamlTo(Context, Ostr); + writeYamlTo(State, Ostr); } return llvm::Error::success(); } Index: tools/llvm-exegesis/llvm-exegesis.cpp =================================================================== --- tools/llvm-exegesis/llvm-exegesis.cpp +++ tools/llvm-exegesis/llvm-exegesis.cpp @@ -104,21 +104,6 @@ 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; -} - // Generates code snippets for opcode `Opcode`. llvm::Expected> generateSnippets(const LLVMState &State, unsigned Opcode) { @@ -180,12 +165,10 @@ if (BenchmarkFile.empty()) BenchmarkFile = "-"; - const BenchmarkResultContext Context = getBenchmarkResultContext(State); - for (const BenchmarkCode &Conf : Configurations) { InstructionBenchmark Result = Runner->runConfiguration(Conf, NumRepetitions); - ExitOnErr(Result.writeYaml(Context, BenchmarkFile)); + ExitOnErr(Result.writeYaml(State, BenchmarkFile)); } exegesis::pfm::pfmTerminate(); } @@ -221,8 +204,7 @@ // Read benchmarks. const LLVMState State; const std::vector Points = - ExitOnErr(InstructionBenchmark::readYamls( - getBenchmarkResultContext(State), BenchmarkFile)); + ExitOnErr(InstructionBenchmark::readYamls(State, BenchmarkFile)); llvm::outs() << "Parsed " << Points.size() << " benchmark points\n"; if (Points.empty()) { llvm::errs() << "no benchmarks to analyze\n"; Index: unittests/tools/llvm-exegesis/CMakeLists.txt =================================================================== --- unittests/tools/llvm-exegesis/CMakeLists.txt +++ unittests/tools/llvm-exegesis/CMakeLists.txt @@ -11,7 +11,6 @@ ) add_llvm_unittest(LLVMExegesisTests - BenchmarkResultTest.cpp BenchmarkRunnerTest.cpp ClusteringTest.cpp PerfHelperTest.cpp Index: unittests/tools/llvm-exegesis/X86/BenchmarkResultTest.cpp =================================================================== --- unittests/tools/llvm-exegesis/X86/BenchmarkResultTest.cpp +++ unittests/tools/llvm-exegesis/X86/BenchmarkResultTest.cpp @@ -8,9 +8,12 @@ //===----------------------------------------------------------------------===// #include "BenchmarkResult.h" +#include "X86InstrInfo.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include "gmock/gmock.h" @@ -47,25 +50,21 @@ namespace { -static constexpr const unsigned kInstrId = 5; -static constexpr const char kInstrName[] = "Instruction5"; -static constexpr const unsigned kReg1Id = 1; -static constexpr const char kReg1Name[] = "Reg1"; -static constexpr const unsigned kReg2Id = 2; -static constexpr const char kReg2Name[] = "Reg2"; - TEST(BenchmarkResultTest, WriteToAndReadFromDisk) { + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86Target(); + LLVMInitializeX86TargetMC(); + + // Read benchmarks. + const LLVMState State; + llvm::ExitOnError ExitOnErr; - BenchmarkResultContext Ctx; - Ctx.addInstrEntry(kInstrId, kInstrName); - Ctx.addRegEntry(kReg1Id, kReg1Name); - Ctx.addRegEntry(kReg2Id, kReg2Name); InstructionBenchmark ToDisk; - ToDisk.Key.Instructions.push_back(llvm::MCInstBuilder(kInstrId) - .addReg(kReg1Id) - .addReg(kReg2Id) + ToDisk.Key.Instructions.push_back(llvm::MCInstBuilder(llvm::X86::XOR32rr) + .addReg(llvm::X86::AL) + .addReg(llvm::X86::AH) .addImm(123) .addFPImm(0.5)); ToDisk.Key.Config = "config"; @@ -83,12 +82,13 @@ EC = llvm::sys::fs::createUniqueDirectory("BenchmarkResultTestDir", Filename); ASSERT_FALSE(EC); llvm::sys::path::append(Filename, "data.yaml"); - ExitOnErr(ToDisk.writeYaml(Ctx, Filename)); + llvm::errs() << Filename << "-------\n"; + ExitOnErr(ToDisk.writeYaml(State, Filename)); { // One-element version. const auto FromDisk = - ExitOnErr(InstructionBenchmark::readYaml(Ctx, Filename)); + ExitOnErr(InstructionBenchmark::readYaml(State, Filename)); EXPECT_THAT(FromDisk.Key.Instructions, Pointwise(EqMCInst(), ToDisk.Key.Instructions)); @@ -104,7 +104,7 @@ { // Vector version. const auto FromDiskVector = - ExitOnErr(InstructionBenchmark::readYamls(Ctx, Filename)); + ExitOnErr(InstructionBenchmark::readYamls(State, Filename)); ASSERT_EQ(FromDiskVector.size(), size_t{1}); const auto FromDisk = FromDiskVector[0]; EXPECT_THAT(FromDisk.Key.Instructions, Index: unittests/tools/llvm-exegesis/X86/CMakeLists.txt =================================================================== --- unittests/tools/llvm-exegesis/X86/CMakeLists.txt +++ unittests/tools/llvm-exegesis/X86/CMakeLists.txt @@ -16,6 +16,7 @@ add_llvm_unittest(LLVMExegesisX86Tests AssemblerTest.cpp AnalysisTest.cpp + BenchmarkResultTest.cpp SnippetGeneratorTest.cpp RegisterAliasingTest.cpp TargetTest.cpp