123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- //===-- BenchmarkResult.cpp -------------------------------------*- C++ -*-===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- #include "BenchmarkResult.h"
- #include "BenchmarkRunner.h"
- #include "Error.h"
- #include "llvm/ADT/STLExtras.h"
- #include "llvm/ADT/ScopeExit.h"
- #include "llvm/ADT/StringMap.h"
- #include "llvm/ADT/StringRef.h"
- #include "llvm/ADT/bit.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";
- static constexpr llvm::StringLiteral kNoRegister("%noreg");
- 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),
- OpcodeNameToOpcodeIdx(
- generateOpcodeNameToOpcodeIdxMapping(State.getInstrInfo())),
- RegNameToRegNo(generateRegNameToRegNoMapping(State.getRegInfo())) {}
- static StringMap<unsigned>
- generateOpcodeNameToOpcodeIdxMapping(const MCInstrInfo &InstrInfo) {
- StringMap<unsigned> Map(InstrInfo.getNumOpcodes());
- for (unsigned I = 0, E = InstrInfo.getNumOpcodes(); I < E; ++I)
- Map[InstrInfo.getName(I)] = I;
- assert(Map.size() == InstrInfo.getNumOpcodes() && "Size prediction failed");
- return Map;
- };
- StringMap<unsigned>
- generateRegNameToRegNoMapping(const MCRegisterInfo &RegInfo) {
- StringMap<unsigned> Map(RegInfo.getNumRegs());
- // Special-case RegNo 0, which would otherwise be spelled as ''.
- Map[kNoRegister] = 0;
- for (unsigned I = 1, E = RegInfo.getNumRegs(); I < E; ++I)
- Map[RegInfo.getName(I)] = I;
- assert(Map.size() == RegInfo.getNumRegs() && "Size prediction failed");
- return Map;
- };
- void serializeMCInst(const MCInst &MCInst, raw_ostream &OS) {
- OS << getInstrName(MCInst.getOpcode());
- for (const auto &Op : MCInst) {
- OS << ' ';
- serializeMCOperand(Op, OS);
- }
- }
- void deserializeMCInst(StringRef String, MCInst &Value) {
- SmallVector<StringRef, 16> Pieces;
- String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
- if (Pieces.empty()) {
- ErrorStream << "Unknown Instruction: '" << String << "'\n";
- return;
- }
- bool ProcessOpcode = true;
- for (StringRef Piece : Pieces) {
- if (ProcessOpcode)
- Value.setOpcode(getInstrOpcode(Piece));
- else
- Value.addOperand(deserializeMCOperand(Piece));
- ProcessOpcode = false;
- }
- }
- std::string &getLastError() { return ErrorStream.str(); }
- raw_string_ostream &getErrorStream() { return ErrorStream; }
- StringRef getRegName(unsigned RegNo) {
- // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
- if (RegNo == 0)
- return kNoRegister;
- const StringRef RegName = State->getRegInfo().getName(RegNo);
- if (RegName.empty())
- ErrorStream << "No register with enum value '" << RegNo << "'\n";
- return RegName;
- }
- Optional<unsigned> getRegNo(StringRef RegName) {
- auto Iter = RegNameToRegNo.find(RegName);
- if (Iter != RegNameToRegNo.end())
- return Iter->second;
- ErrorStream << "No register with name '" << RegName << "'\n";
- return None;
- }
- private:
- void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {
- OS << kIntegerPrefix;
- OS.write_hex(bit_cast<uint64_t>(Value));
- }
- bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) {
- if (!String.consume_front(kIntegerPrefix))
- return false;
- return !String.consumeInteger(16, Value);
- }
- void serializeFPOperand(raw_ostream &OS, double Value) {
- OS << kDoublePrefix << format("%la", Value);
- }
- bool tryDeserializeFPOperand(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 MCOperand &MCOperand, raw_ostream &OS) {
- if (MCOperand.isReg()) {
- OS << getRegName(MCOperand.getReg());
- } else if (MCOperand.isImm()) {
- serializeIntegerOperand(OS, MCOperand.getImm());
- } else if (MCOperand.isDFPImm()) {
- serializeFPOperand(OS, bit_cast<double>(MCOperand.getDFPImm()));
- } else {
- OS << kInvalidOperand;
- }
- }
- MCOperand deserializeMCOperand(StringRef String) {
- assert(!String.empty());
- int64_t IntValue = 0;
- double DoubleValue = 0;
- if (tryDeserializeIntegerOperand(String, IntValue))
- return MCOperand::createImm(IntValue);
- if (tryDeserializeFPOperand(String, DoubleValue))
- return MCOperand::createDFPImm(bit_cast<uint64_t>(DoubleValue));
- if (auto RegNo = getRegNo(String))
- return MCOperand::createReg(*RegNo);
- if (String != kInvalidOperand)
- ErrorStream << "Unknown Operand: '" << String << "'\n";
- return {};
- }
- StringRef getInstrName(unsigned InstrNo) {
- const StringRef InstrName = State->getInstrInfo().getName(InstrNo);
- if (InstrName.empty())
- ErrorStream << "No opcode with enum value '" << InstrNo << "'\n";
- return InstrName;
- }
- unsigned getInstrOpcode(StringRef InstrName) {
- auto Iter = OpcodeNameToOpcodeIdx.find(InstrName);
- if (Iter != OpcodeNameToOpcodeIdx.end())
- return Iter->second;
- ErrorStream << "No opcode with name '" << InstrName << "'\n";
- return 0;
- }
- const exegesis::LLVMState *State;
- std::string LastError;
- raw_string_ostream ErrorStream;
- const StringMap<unsigned> OpcodeNameToOpcodeIdx;
- const StringMap<unsigned> RegNameToRegNo;
- };
- } // namespace
- // Defining YAML traits for IO.
- namespace yaml {
- static YamlContext &getTypedContext(void *Ctx) {
- return *reinterpret_cast<YamlContext *>(Ctx);
- }
- // std::vector<MCInst> will be rendered as a list.
- template <> struct SequenceElementTraits<MCInst> {
- static const bool flow = false;
- };
- template <> struct ScalarTraits<MCInst> {
- static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) {
- getTypedContext(Ctx).serializeMCInst(Value, Out);
- }
- static StringRef input(StringRef Scalar, void *Ctx, 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);
- Io.enumCase(Value, "inverse_throughput",
- exegesis::InstructionBenchmark::InverseThroughput);
- }
- };
- // 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,
- raw_ostream &Out) {
- YamlContext &Context = getTypedContext(Ctx);
- Out << Context.getRegName(RV.Register) << "=0x"
- << toString(RV.Value, kRadix, kSigned);
- }
- static StringRef input(StringRef String, void *Ctx,
- exegesis::RegisterValue &RV) {
- SmallVector<StringRef, 2> Pieces;
- String.split(Pieces, "=0x", /* MaxSplit */ -1,
- /* KeepEmpty */ false);
- YamlContext &Context = getTypedContext(Ctx);
- Optional<unsigned> RegNo;
- if (Pieces.size() == 2 && (RegNo = Context.getRegNo(Pieces[0]))) {
- RV.Register = *RegNo;
- const unsigned BitsNeeded = APInt::getBitsNeeded(Pieces[1], kRadix);
- RV.Value = 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 {
- Expected<InstructionBenchmark>
- InstructionBenchmark::readYaml(const LLVMState &State, StringRef Filename) {
- if (auto ExpectedMemoryBuffer =
- errorOrToExpected(MemoryBuffer::getFile(Filename, /*IsText=*/true))) {
- yaml::Input Yin(*ExpectedMemoryBuffer.get());
- YamlContext Context(State);
- InstructionBenchmark Benchmark;
- if (Yin.setCurrentDocument())
- yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context);
- if (!Context.getLastError().empty())
- return make_error<Failure>(Context.getLastError());
- return Benchmark;
- } else {
- return ExpectedMemoryBuffer.takeError();
- }
- }
- Expected<std::vector<InstructionBenchmark>>
- InstructionBenchmark::readYamls(const LLVMState &State, StringRef Filename) {
- if (auto ExpectedMemoryBuffer =
- errorOrToExpected(MemoryBuffer::getFile(Filename, /*IsText=*/true))) {
- 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 errorCodeToError(Yin.error());
- if (!Context.getLastError().empty())
- return make_error<Failure>(Context.getLastError());
- Yin.nextDocument();
- }
- return Benchmarks;
- } else {
- return ExpectedMemoryBuffer.takeError();
- }
- }
- Error InstructionBenchmark::writeYamlTo(const LLVMState &State,
- raw_ostream &OS) {
- auto Cleanup = make_scope_exit([&] { OS.flush(); });
- yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/);
- YamlContext Context(State);
- Yout.beginDocuments();
- yaml::yamlize(Yout, *this, /*unused*/ true, Context);
- if (!Context.getLastError().empty())
- return make_error<Failure>(Context.getLastError());
- Yout.endDocuments();
- return Error::success();
- }
- Error InstructionBenchmark::readYamlFrom(const LLVMState &State,
- StringRef InputContent) {
- yaml::Input Yin(InputContent);
- YamlContext Context(State);
- if (Yin.setCurrentDocument())
- yaml::yamlize(Yin, *this, /*unused*/ true, Context);
- if (!Context.getLastError().empty())
- return make_error<Failure>(Context.getLastError());
- return Error::success();
- }
- Error InstructionBenchmark::writeYaml(const LLVMState &State,
- const StringRef Filename) {
- if (Filename == "-") {
- if (auto Err = writeYamlTo(State, outs()))
- return Err;
- } else {
- int ResultFD = 0;
- if (auto E = errorCodeToError(openFileForWrite(Filename, ResultFD,
- sys::fs::CD_CreateAlways,
- sys::fs::OF_TextWithCRLF))) {
- return E;
- }
- raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/);
- if (auto Err = writeYamlTo(State, Ostr))
- return Err;
- }
- return 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);
- }
- bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) {
- return std::tie(A.Key, A.PerInstructionValue, A.PerSnippetValue) ==
- std::tie(B.Key, B.PerInstructionValue, B.PerSnippetValue);
- }
- } // namespace exegesis
- } // namespace llvm
|