1 #ifndef LLVM_PROFILEDATA_MEMPROFYAML_H_ 2 #define LLVM_PROFILEDATA_MEMPROFYAML_H_ 3 4 #include "llvm/ADT/SmallVector.h" 5 #include "llvm/ProfileData/DataAccessProf.h" 6 #include "llvm/ProfileData/MemProf.h" 7 #include "llvm/Support/Format.h" 8 #include "llvm/Support/YAMLTraits.h" 9 10 namespace llvm { 11 namespace memprof { 12 // A "typedef" for GUID. See ScalarTraits<memprof::GUIDHex64> for how a GUID is 13 // serialized and deserialized in YAML. 14 LLVM_YAML_STRONG_TYPEDEF(uint64_t, GUIDHex64) 15 16 // Helper struct for AllMemProfData. In YAML, we treat the GUID and the fields 17 // within MemProfRecord at the same level as if the GUID were part of 18 // MemProfRecord. 19 struct GUIDMemProfRecordPair { 20 GUIDHex64 GUID; 21 MemProfRecord Record; 22 }; 23 24 // Helper struct to yamlify memprof::DataAccessProfData. The struct 25 // members use owned strings. This is for simplicity and assumes that most real 26 // world use cases do look-ups and regression test scale is small. 27 struct YamlDataAccessProfData { 28 std::vector<memprof::DataAccessProfRecord> Records; 29 std::vector<uint64_t> KnownColdStrHashes; 30 std::vector<std::string> KnownColdSymbols; 31 isEmptyYamlDataAccessProfData32 bool isEmpty() const { 33 return Records.empty() && KnownColdStrHashes.empty() && 34 KnownColdSymbols.empty(); 35 } 36 }; 37 38 // The top-level data structure, only used with YAML for now. 39 struct AllMemProfData { 40 std::vector<GUIDMemProfRecordPair> HeapProfileRecords; 41 YamlDataAccessProfData YamlifiedDataAccessProfiles; 42 }; 43 } // namespace memprof 44 45 namespace yaml { 46 template <> struct ScalarTraits<memprof::GUIDHex64> { 47 static void output(const memprof::GUIDHex64 &Val, void *, raw_ostream &Out) { 48 // Print GUID as a hexadecimal number with 0x prefix, no padding to keep 49 // test strings compact. 50 Out << format("0x%" PRIx64, (uint64_t)Val); 51 } 52 static StringRef input(StringRef Scalar, void *, memprof::GUIDHex64 &Val) { 53 // Reject decimal GUIDs. 54 if (all_of(Scalar, [](char C) { return std::isdigit(C); })) 55 return "use a hexadecimal GUID or a function instead"; 56 57 uint64_t Num; 58 if (Scalar.starts_with_insensitive("0x")) { 59 // Accept hexadecimal numbers starting with 0x or 0X. 60 if (Scalar.getAsInteger(0, Num)) 61 return "invalid hex64 number"; 62 Val = Num; 63 } else { 64 // Otherwise, treat the input as a string containing a function name. 65 Val = memprof::getGUID(Scalar); 66 } 67 return StringRef(); 68 } 69 static QuotingType mustQuote(StringRef) { return QuotingType::None; } 70 }; 71 72 template <> struct MappingTraits<memprof::Frame> { 73 // Essentially the same as memprof::Frame except that Function is of type 74 // memprof::GUIDHex64 instead of GlobalValue::GUID. This class helps in two 75 // ways. During serialization, we print Function as a 16-digit hexadecimal 76 // number. During deserialization, we accept a function name as an 77 // alternative to the usual GUID expressed as a hexadecimal number. 78 class FrameWithHex64 { 79 public: 80 FrameWithHex64(IO &) {} 81 FrameWithHex64(IO &, const memprof::Frame &F) 82 : Function(F.Function), LineOffset(F.LineOffset), Column(F.Column), 83 IsInlineFrame(F.IsInlineFrame) {} 84 memprof::Frame denormalize(IO &) { 85 return memprof::Frame(Function, LineOffset, Column, IsInlineFrame); 86 } 87 88 memprof::GUIDHex64 Function = 0; 89 static_assert(std::is_same_v<decltype(Function.value), 90 decltype(memprof::Frame::Function)>); 91 decltype(memprof::Frame::LineOffset) LineOffset = 0; 92 decltype(memprof::Frame::Column) Column = 0; 93 decltype(memprof::Frame::IsInlineFrame) IsInlineFrame = false; 94 }; 95 96 static void mapping(IO &Io, memprof::Frame &F) { 97 MappingNormalization<FrameWithHex64, memprof::Frame> Keys(Io, F); 98 99 Io.mapRequired("Function", Keys->Function); 100 Io.mapRequired("LineOffset", Keys->LineOffset); 101 Io.mapRequired("Column", Keys->Column); 102 Io.mapRequired("IsInlineFrame", Keys->IsInlineFrame); 103 104 // Assert that the definition of Frame matches what we expect. The 105 // structured bindings below detect changes to the number of fields. 106 // static_assert checks the type of each field. 107 const auto &[Function, SymbolName, LineOffset, Column, IsInlineFrame] = F; 108 static_assert( 109 std::is_same_v<remove_cvref_t<decltype(Function)>, GlobalValue::GUID>); 110 static_assert(std::is_same_v<remove_cvref_t<decltype(SymbolName)>, 111 std::unique_ptr<std::string>>); 112 static_assert( 113 std::is_same_v<remove_cvref_t<decltype(LineOffset)>, uint32_t>); 114 static_assert(std::is_same_v<remove_cvref_t<decltype(Column)>, uint32_t>); 115 static_assert( 116 std::is_same_v<remove_cvref_t<decltype(IsInlineFrame)>, bool>); 117 118 // MSVC issues unused variable warnings despite the uses in static_assert 119 // above. 120 (void)Function; 121 (void)SymbolName; 122 (void)LineOffset; 123 (void)Column; 124 (void)IsInlineFrame; 125 } 126 127 // Request the inline notation for brevity: 128 // { Function: 123, LineOffset: 11, Column: 10; IsInlineFrame: true } 129 static const bool flow = true; 130 }; 131 132 template <> struct CustomMappingTraits<memprof::PortableMemInfoBlock> { 133 static void inputOne(IO &Io, StringRef KeyStr, 134 memprof::PortableMemInfoBlock &MIB) { 135 // PortableMemInfoBlock keeps track of the set of fields that actually have 136 // values. We update the set here as we receive a key-value pair from the 137 // YAML document. 138 // 139 // We set MIB.Name via a temporary variable because ScalarTraits<uintptr_t> 140 // isn't available on macOS. 141 #define MIBEntryDef(NameTag, Name, Type) \ 142 if (KeyStr == #Name) { \ 143 uint64_t Value; \ 144 Io.mapRequired(KeyStr.str().c_str(), Value); \ 145 MIB.Name = static_cast<Type>(Value); \ 146 MIB.Schema.set(llvm::to_underlying(memprof::Meta::Name)); \ 147 return; \ 148 } 149 #include "llvm/ProfileData/MIBEntryDef.inc" 150 #undef MIBEntryDef 151 Io.setError("Key is not a valid validation event"); 152 } 153 154 static void output(IO &Io, memprof::PortableMemInfoBlock &MIB) { 155 auto Schema = MIB.getSchema(); 156 #define MIBEntryDef(NameTag, Name, Type) \ 157 if (Schema.test(llvm::to_underlying(memprof::Meta::Name))) { \ 158 uint64_t Value = MIB.Name; \ 159 Io.mapRequired(#Name, Value); \ 160 } 161 #include "llvm/ProfileData/MIBEntryDef.inc" 162 #undef MIBEntryDef 163 } 164 }; 165 166 template <> struct MappingTraits<memprof::AllocationInfo> { 167 static void mapping(IO &Io, memprof::AllocationInfo &AI) { 168 Io.mapRequired("Callstack", AI.CallStack); 169 Io.mapRequired("MemInfoBlock", AI.Info); 170 } 171 }; 172 173 // In YAML, we use GUIDMemProfRecordPair instead of MemProfRecord so that we can 174 // treat the GUID and the fields within MemProfRecord at the same level as if 175 // the GUID were part of MemProfRecord. 176 template <> struct MappingTraits<memprof::CallSiteInfo> { 177 // Helper class to normalize CalleeGuids to use GUIDHex64 for YAML I/O. 178 class CallSiteInfoWithHex64Guids { 179 public: 180 CallSiteInfoWithHex64Guids(IO &) {} 181 CallSiteInfoWithHex64Guids(IO &, const memprof::CallSiteInfo &CS) 182 : Frames(CS.Frames) { 183 // Convert uint64_t GUIDs to GUIDHex64 for serialization. 184 CalleeGuids.reserve(CS.CalleeGuids.size()); 185 for (uint64_t Guid : CS.CalleeGuids) 186 CalleeGuids.push_back(memprof::GUIDHex64(Guid)); 187 } 188 189 memprof::CallSiteInfo denormalize(IO &) { 190 memprof::CallSiteInfo CS; 191 CS.Frames = Frames; 192 // Convert GUIDHex64 back to uint64_t GUIDs after deserialization. 193 CS.CalleeGuids.reserve(CalleeGuids.size()); 194 for (memprof::GUIDHex64 HexGuid : CalleeGuids) 195 CS.CalleeGuids.push_back(HexGuid.value); 196 return CS; 197 } 198 199 // Keep Frames as is, since MappingTraits<memprof::Frame> handles its 200 // Function GUID. 201 decltype(memprof::CallSiteInfo::Frames) Frames; 202 // Use a vector of GUIDHex64 for CalleeGuids to leverage its ScalarTraits. 203 SmallVector<memprof::GUIDHex64> CalleeGuids; 204 }; 205 206 static void mapping(IO &Io, memprof::CallSiteInfo &CS) { 207 // Use MappingNormalization to handle the conversion between 208 // memprof::CallSiteInfo and CallSiteInfoWithHex64Guids. 209 MappingNormalization<CallSiteInfoWithHex64Guids, memprof::CallSiteInfo> 210 Keys(Io, CS); 211 Io.mapRequired("Frames", Keys->Frames); 212 // Map the normalized CalleeGuids (which are now GUIDHex64). 213 Io.mapOptional("CalleeGuids", Keys->CalleeGuids); 214 } 215 }; 216 217 template <> struct MappingTraits<memprof::GUIDMemProfRecordPair> { 218 static void mapping(IO &Io, memprof::GUIDMemProfRecordPair &Pair) { 219 Io.mapRequired("GUID", Pair.GUID); 220 Io.mapRequired("AllocSites", Pair.Record.AllocSites); 221 Io.mapRequired("CallSites", Pair.Record.CallSites); 222 } 223 }; 224 225 template <> struct MappingTraits<memprof::SourceLocation> { 226 static void mapping(IO &Io, memprof::SourceLocation &Loc) { 227 Io.mapOptional("FileName", Loc.FileName); 228 Io.mapOptional("Line", Loc.Line); 229 } 230 }; 231 232 template <> struct MappingTraits<memprof::DataAccessProfRecord> { 233 static void mapping(IO &Io, memprof::DataAccessProfRecord &Rec) { 234 if (Io.outputting()) { 235 if (std::holds_alternative<std::string>(Rec.SymHandle)) { 236 Io.mapOptional("Symbol", std::get<std::string>(Rec.SymHandle)); 237 } else { 238 Io.mapOptional("Hash", std::get<uint64_t>(Rec.SymHandle)); 239 } 240 } else { 241 std::string SymName; 242 uint64_t Hash = 0; 243 Io.mapOptional("Symbol", SymName); 244 Io.mapOptional("Hash", Hash); 245 if (!SymName.empty()) { 246 Rec.SymHandle = SymName; 247 } else { 248 Rec.SymHandle = Hash; 249 } 250 } 251 Io.mapRequired("AccessCount", Rec.AccessCount); 252 Io.mapOptional("Locations", Rec.Locations); 253 } 254 }; 255 256 template <> struct MappingTraits<memprof::YamlDataAccessProfData> { 257 static void mapping(IO &Io, memprof::YamlDataAccessProfData &Data) { 258 Io.mapOptional("SampledRecords", Data.Records); 259 Io.mapOptional("KnownColdSymbols", Data.KnownColdSymbols); 260 Io.mapOptional("KnownColdStrHashes", Data.KnownColdStrHashes); 261 } 262 }; 263 264 template <> struct MappingTraits<memprof::AllMemProfData> { 265 static void mapping(IO &Io, memprof::AllMemProfData &Data) { 266 Io.mapRequired("HeapProfileRecords", Data.HeapProfileRecords); 267 // Map data access profiles if reading input, or if writing output && 268 // the struct is populated. 269 if (!Io.outputting() || !Data.YamlifiedDataAccessProfiles.isEmpty()) 270 Io.mapOptional("DataAccessProfiles", Data.YamlifiedDataAccessProfiles); 271 } 272 }; 273 274 template <> struct SequenceTraits<SmallVector<memprof::GUIDHex64>> { 275 static size_t size(IO &io, SmallVector<memprof::GUIDHex64> &Seq) { 276 return Seq.size(); 277 } 278 static memprof::GUIDHex64 & 279 element(IO &io, SmallVector<memprof::GUIDHex64> &Seq, size_t Index) { 280 if (Index >= Seq.size()) 281 Seq.resize(Index + 1); 282 return Seq[Index]; 283 } 284 static const bool flow = true; 285 }; 286 287 } // namespace yaml 288 } // namespace llvm 289 290 LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::Frame) 291 LLVM_YAML_IS_SEQUENCE_VECTOR(std::vector<memprof::Frame>) 292 LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::AllocationInfo) 293 LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::CallSiteInfo) 294 LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::GUIDMemProfRecordPair) 295 LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::GUIDHex64) // Used for CalleeGuids 296 LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::DataAccessProfRecord) 297 LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::SourceLocation) 298 299 #endif // LLVM_PROFILEDATA_MEMPROFYAML_H_ 300