1 //===-- BenchmarkResult.cpp -------------------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "BenchmarkResult.h" 11 #include "llvm/ADT/STLExtras.h" 12 #include "llvm/ADT/StringRef.h" 13 #include "llvm/ObjectYAML/YAML.h" 14 #include "llvm/Support/FileOutputBuffer.h" 15 #include "llvm/Support/FileSystem.h" 16 #include "llvm/Support/Format.h" 17 #include "llvm/Support/raw_ostream.h" 18 19 static constexpr const char kIntegerFormat[] = "i_0x%" PRId64 "x"; 20 static constexpr const char kDoubleFormat[] = "f_%la"; 21 22 static void serialize(const exegesis::BenchmarkResultContext &Context, 23 const llvm::MCOperand &MCOperand, llvm::raw_ostream &OS) { 24 if (MCOperand.isReg()) { 25 OS << Context.getRegName(MCOperand.getReg()); 26 } else if (MCOperand.isImm()) { 27 OS << llvm::format(kIntegerFormat, MCOperand.getImm()); 28 } else if (MCOperand.isFPImm()) { 29 OS << llvm::format(kDoubleFormat, MCOperand.getFPImm()); 30 } else { 31 OS << "INVALID"; 32 } 33 } 34 35 static void serialize(const exegesis::BenchmarkResultContext &Context, 36 const llvm::MCInst &MCInst, llvm::raw_ostream &OS) { 37 OS << Context.getInstrName(MCInst.getOpcode()); 38 for (const auto &Op : MCInst) { 39 OS << ' '; 40 serialize(Context, Op, OS); 41 } 42 } 43 44 static llvm::MCOperand 45 deserialize(const exegesis::BenchmarkResultContext &Context, 46 llvm::StringRef String) { 47 assert(!String.empty()); 48 int64_t IntValue = 0; 49 double DoubleValue = 0; 50 if (sscanf(String.data(), kIntegerFormat, &IntValue) == 1) 51 return llvm::MCOperand::createImm(IntValue); 52 if (sscanf(String.data(), kDoubleFormat, &DoubleValue) == 1) 53 return llvm::MCOperand::createFPImm(DoubleValue); 54 if (unsigned RegNo = Context.getRegNo(String)) // Returns 0 if invalid. 55 return llvm::MCOperand::createReg(RegNo); 56 return {}; 57 } 58 59 static llvm::StringRef 60 deserialize(const exegesis::BenchmarkResultContext &Context, 61 llvm::StringRef String, llvm::MCInst &Value) { 62 llvm::SmallVector<llvm::StringRef, 8> Pieces; 63 String.split(Pieces, " "); 64 if (Pieces.empty()) 65 return "Invalid Instruction"; 66 bool ProcessOpcode = true; 67 for (llvm::StringRef Piece : Pieces) { 68 if (ProcessOpcode) { 69 ProcessOpcode = false; 70 Value.setOpcode(Context.getInstrOpcode(Piece)); 71 if (Value.getOpcode() == 0) 72 return "Unknown Opcode Name"; 73 } else { 74 Value.addOperand(deserialize(Context, Piece)); 75 } 76 } 77 return {}; 78 } 79 80 // YAML IO requires a mutable pointer to Context but we guarantee to not 81 // modify it. 82 static void *getUntypedContext(const exegesis::BenchmarkResultContext &Ctx) { 83 return const_cast<exegesis::BenchmarkResultContext *>(&Ctx); 84 } 85 86 static const exegesis::BenchmarkResultContext &getTypedContext(void *Ctx) { 87 assert(Ctx); 88 return *static_cast<const exegesis::BenchmarkResultContext *>(Ctx); 89 } 90 91 // Defining YAML traits for IO. 92 namespace llvm { 93 namespace yaml { 94 95 // std::vector<llvm::MCInst> will be rendered as a list. 96 template <> struct SequenceElementTraits<llvm::MCInst> { 97 static const bool flow = false; 98 }; 99 100 template <> struct ScalarTraits<llvm::MCInst> { 101 102 static void output(const llvm::MCInst &Value, void *Ctx, 103 llvm::raw_ostream &Out) { 104 serialize(getTypedContext(Ctx), Value, Out); 105 } 106 107 static StringRef input(StringRef Scalar, void *Ctx, llvm::MCInst &Value) { 108 return deserialize(getTypedContext(Ctx), Scalar, Value); 109 } 110 111 static QuotingType mustQuote(StringRef) { return QuotingType::Single; } 112 113 static const bool flow = true; 114 }; 115 116 // std::vector<exegesis::Measure> will be rendered as a list. 117 template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> { 118 static const bool flow = false; 119 }; 120 121 // exegesis::Measure is rendererd as a flow instead of a list. 122 // e.g. { "key": "the key", "value": 0123 } 123 template <> struct MappingTraits<exegesis::BenchmarkMeasure> { 124 static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) { 125 Io.mapRequired("key", Obj.Key); 126 Io.mapRequired("value", Obj.Value); 127 Io.mapOptional("debug_string", Obj.DebugString); 128 } 129 static const bool flow = true; 130 }; 131 132 template <> 133 struct ScalarEnumerationTraits<exegesis::InstructionBenchmark::ModeE> { 134 static void enumeration(IO &Io, 135 exegesis::InstructionBenchmark::ModeE &Value) { 136 Io.enumCase(Value, "", exegesis::InstructionBenchmark::Unknown); 137 Io.enumCase(Value, "latency", exegesis::InstructionBenchmark::Latency); 138 Io.enumCase(Value, "uops", exegesis::InstructionBenchmark::Uops); 139 } 140 }; 141 142 template <> struct MappingTraits<exegesis::InstructionBenchmarkKey> { 143 static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj) { 144 Io.mapRequired("instructions", Obj.Instructions); 145 Io.mapOptional("config", Obj.Config); 146 } 147 }; 148 149 template <> struct MappingTraits<exegesis::InstructionBenchmark> { 150 class NormalizedBinary { 151 public: 152 NormalizedBinary(IO &io) {} 153 NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {} 154 std::vector<uint8_t> denormalize(IO &) { 155 std::vector<uint8_t> Data; 156 std::string Str; 157 raw_string_ostream OSS(Str); 158 Binary.writeAsBinary(OSS); 159 OSS.flush(); 160 Data.assign(Str.begin(), Str.end()); 161 return Data; 162 } 163 164 BinaryRef Binary; 165 }; 166 167 static void mapping(IO &Io, exegesis::InstructionBenchmark &Obj) { 168 Io.mapRequired("mode", Obj.Mode); 169 Io.mapRequired("key", Obj.Key); 170 Io.mapRequired("cpu_name", Obj.CpuName); 171 Io.mapRequired("llvm_triple", Obj.LLVMTriple); 172 Io.mapRequired("num_repetitions", Obj.NumRepetitions); 173 Io.mapRequired("measurements", Obj.Measurements); 174 Io.mapRequired("error", Obj.Error); 175 Io.mapOptional("info", Obj.Info); 176 // AssembledSnippet 177 MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString( 178 Io, Obj.AssembledSnippet); 179 Io.mapOptional("assembled_snippet", BinaryString->Binary); 180 } 181 }; 182 183 } // namespace yaml 184 } // namespace llvm 185 186 LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(exegesis::InstructionBenchmark) 187 188 namespace exegesis { 189 190 void BenchmarkResultContext::addRegEntry(unsigned RegNo, llvm::StringRef Name) { 191 assert(RegNoToName.find(RegNo) == RegNoToName.end()); 192 assert(RegNameToNo.find(Name) == RegNameToNo.end()); 193 RegNoToName[RegNo] = Name; 194 RegNameToNo[Name] = RegNo; 195 } 196 197 llvm::StringRef BenchmarkResultContext::getRegName(unsigned RegNo) const { 198 const auto Itr = RegNoToName.find(RegNo); 199 if (Itr != RegNoToName.end()) 200 return Itr->second; 201 return {}; 202 } 203 204 unsigned BenchmarkResultContext::getRegNo(llvm::StringRef Name) const { 205 const auto Itr = RegNameToNo.find(Name); 206 if (Itr != RegNameToNo.end()) 207 return Itr->second; 208 return 0; 209 } 210 211 void BenchmarkResultContext::addInstrEntry(unsigned Opcode, 212 llvm::StringRef Name) { 213 assert(InstrOpcodeToName.find(Opcode) == InstrOpcodeToName.end()); 214 assert(InstrNameToOpcode.find(Name) == InstrNameToOpcode.end()); 215 InstrOpcodeToName[Opcode] = Name; 216 InstrNameToOpcode[Name] = Opcode; 217 } 218 219 llvm::StringRef BenchmarkResultContext::getInstrName(unsigned Opcode) const { 220 const auto Itr = InstrOpcodeToName.find(Opcode); 221 if (Itr != InstrOpcodeToName.end()) 222 return Itr->second; 223 return {}; 224 } 225 226 unsigned BenchmarkResultContext::getInstrOpcode(llvm::StringRef Name) const { 227 const auto Itr = InstrNameToOpcode.find(Name); 228 if (Itr != InstrNameToOpcode.end()) 229 return Itr->second; 230 return 0; 231 } 232 233 template <typename ObjectOrList> 234 static llvm::Expected<ObjectOrList> 235 readYamlCommon(const BenchmarkResultContext &Context, 236 llvm::StringRef Filename) { 237 if (auto ExpectedMemoryBuffer = 238 llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) { 239 std::unique_ptr<llvm::MemoryBuffer> MemoryBuffer = 240 std::move(ExpectedMemoryBuffer.get()); 241 llvm::yaml::Input Yin(*MemoryBuffer, getUntypedContext(Context)); 242 ObjectOrList Benchmark; 243 Yin >> Benchmark; 244 return Benchmark; 245 } else { 246 return ExpectedMemoryBuffer.takeError(); 247 } 248 } 249 250 llvm::Expected<InstructionBenchmark> 251 InstructionBenchmark::readYaml(const BenchmarkResultContext &Context, 252 llvm::StringRef Filename) { 253 return readYamlCommon<InstructionBenchmark>(Context, Filename); 254 } 255 256 llvm::Expected<std::vector<InstructionBenchmark>> 257 InstructionBenchmark::readYamls(const BenchmarkResultContext &Context, 258 llvm::StringRef Filename) { 259 return readYamlCommon<std::vector<InstructionBenchmark>>(Context, Filename); 260 } 261 262 void InstructionBenchmark::writeYamlTo(const BenchmarkResultContext &Context, 263 llvm::raw_ostream &OS) { 264 llvm::yaml::Output Yout(OS, getUntypedContext(Context)); 265 Yout << *this; 266 } 267 268 void InstructionBenchmark::readYamlFrom(const BenchmarkResultContext &Context, 269 llvm::StringRef InputContent) { 270 llvm::yaml::Input Yin(InputContent, getUntypedContext(Context)); 271 Yin >> *this; 272 } 273 274 llvm::Error 275 InstructionBenchmark::writeYaml(const BenchmarkResultContext &Context, 276 const llvm::StringRef Filename) { 277 if (Filename == "-") { 278 writeYamlTo(Context, llvm::outs()); 279 } else { 280 int ResultFD = 0; 281 if (auto E = llvm::errorCodeToError( 282 openFileForWrite(Filename, ResultFD, llvm::sys::fs::CD_CreateAlways, 283 llvm::sys::fs::F_Text))) { 284 return E; 285 } 286 llvm::raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/); 287 writeYamlTo(Context, Ostr); 288 } 289 return llvm::Error::success(); 290 } 291 292 void BenchmarkMeasureStats::push(const BenchmarkMeasure &BM) { 293 if (Key.empty()) 294 Key = BM.Key; 295 assert(Key == BM.Key); 296 ++NumValues; 297 SumValues += BM.Value; 298 MaxValue = std::max(MaxValue, BM.Value); 299 MinValue = std::min(MinValue, BM.Value); 300 } 301 302 } // namespace exegesis 303