#ifndef LLVM_PROFILEDATA_MEMPROFYAML_H_ #define LLVM_PROFILEDATA_MEMPROFYAML_H_ #include "llvm/ADT/SmallVector.h" #include "llvm/ProfileData/DataAccessProf.h" #include "llvm/ProfileData/MemProf.h" #include "llvm/Support/Format.h" #include "llvm/Support/YAMLTraits.h" namespace llvm { namespace memprof { // A "typedef" for GUID. See ScalarTraits for how a GUID is // serialized and deserialized in YAML. LLVM_YAML_STRONG_TYPEDEF(uint64_t, GUIDHex64) // Helper struct for AllMemProfData. In YAML, we treat the GUID and the fields // within MemProfRecord at the same level as if the GUID were part of // MemProfRecord. struct GUIDMemProfRecordPair { GUIDHex64 GUID; MemProfRecord Record; }; // Helper struct to yamlify memprof::DataAccessProfData. The struct // members use owned strings. This is for simplicity and assumes that most real // world use cases do look-ups and regression test scale is small. struct YamlDataAccessProfData { std::vector Records; std::vector KnownColdStrHashes; std::vector KnownColdSymbols; bool isEmpty() const { return Records.empty() && KnownColdStrHashes.empty() && KnownColdSymbols.empty(); } }; // The top-level data structure, only used with YAML for now. struct AllMemProfData { std::vector HeapProfileRecords; YamlDataAccessProfData YamlifiedDataAccessProfiles; }; } // namespace memprof namespace yaml { template <> struct ScalarTraits { static void output(const memprof::GUIDHex64 &Val, void *, raw_ostream &Out) { // Print GUID as a hexadecimal number with 0x prefix, no padding to keep // test strings compact. Out << format("0x%" PRIx64, (uint64_t)Val); } static StringRef input(StringRef Scalar, void *, memprof::GUIDHex64 &Val) { // Reject decimal GUIDs. if (all_of(Scalar, [](char C) { return std::isdigit(C); })) return "use a hexadecimal GUID or a function instead"; uint64_t Num; if (Scalar.starts_with_insensitive("0x")) { // Accept hexadecimal numbers starting with 0x or 0X. if (Scalar.getAsInteger(0, Num)) return "invalid hex64 number"; Val = Num; } else { // Otherwise, treat the input as a string containing a function name. Val = memprof::getGUID(Scalar); } return StringRef(); } static QuotingType mustQuote(StringRef) { return QuotingType::None; } }; template <> struct MappingTraits { // Essentially the same as memprof::Frame except that Function is of type // memprof::GUIDHex64 instead of GlobalValue::GUID. This class helps in two // ways. During serialization, we print Function as a 16-digit hexadecimal // number. During deserialization, we accept a function name as an // alternative to the usual GUID expressed as a hexadecimal number. class FrameWithHex64 { public: FrameWithHex64(IO &) {} FrameWithHex64(IO &, const memprof::Frame &F) : Function(F.Function), LineOffset(F.LineOffset), Column(F.Column), IsInlineFrame(F.IsInlineFrame) {} memprof::Frame denormalize(IO &) { return memprof::Frame(Function, LineOffset, Column, IsInlineFrame); } memprof::GUIDHex64 Function = 0; static_assert(std::is_same_v); decltype(memprof::Frame::LineOffset) LineOffset = 0; decltype(memprof::Frame::Column) Column = 0; decltype(memprof::Frame::IsInlineFrame) IsInlineFrame = false; }; static void mapping(IO &Io, memprof::Frame &F) { MappingNormalization Keys(Io, F); Io.mapRequired("Function", Keys->Function); Io.mapRequired("LineOffset", Keys->LineOffset); Io.mapRequired("Column", Keys->Column); Io.mapRequired("IsInlineFrame", Keys->IsInlineFrame); // Assert that the definition of Frame matches what we expect. The // structured bindings below detect changes to the number of fields. // static_assert checks the type of each field. const auto &[Function, SymbolName, LineOffset, Column, IsInlineFrame] = F; static_assert( std::is_same_v, GlobalValue::GUID>); static_assert(std::is_same_v, std::unique_ptr>); static_assert( std::is_same_v, uint32_t>); static_assert(std::is_same_v, uint32_t>); static_assert( std::is_same_v, bool>); // MSVC issues unused variable warnings despite the uses in static_assert // above. (void)Function; (void)SymbolName; (void)LineOffset; (void)Column; (void)IsInlineFrame; } // Request the inline notation for brevity: // { Function: 123, LineOffset: 11, Column: 10; IsInlineFrame: true } static const bool flow = true; }; template <> struct CustomMappingTraits { static void inputOne(IO &Io, StringRef KeyStr, memprof::PortableMemInfoBlock &MIB) { // PortableMemInfoBlock keeps track of the set of fields that actually have // values. We update the set here as we receive a key-value pair from the // YAML document. // // We set MIB.Name via a temporary variable because ScalarTraits // isn't available on macOS. #define MIBEntryDef(NameTag, Name, Type) \ if (KeyStr == #Name) { \ uint64_t Value; \ Io.mapRequired(KeyStr, Value); \ MIB.Name = static_cast(Value); \ MIB.Schema.set(llvm::to_underlying(memprof::Meta::Name)); \ return; \ } #include "llvm/ProfileData/MIBEntryDef.inc" #undef MIBEntryDef Io.setError("Key is not a valid validation event"); } static void output(IO &Io, memprof::PortableMemInfoBlock &MIB) { auto Schema = MIB.getSchema(); #define MIBEntryDef(NameTag, Name, Type) \ if (Schema.test(llvm::to_underlying(memprof::Meta::Name))) { \ uint64_t Value = MIB.Name; \ Io.mapRequired(#Name, Value); \ } #include "llvm/ProfileData/MIBEntryDef.inc" #undef MIBEntryDef } }; template <> struct MappingTraits { static void mapping(IO &Io, memprof::AllocationInfo &AI) { Io.mapRequired("Callstack", AI.CallStack); Io.mapRequired("MemInfoBlock", AI.Info); } }; // In YAML, we use GUIDMemProfRecordPair instead of MemProfRecord so that we can // treat the GUID and the fields within MemProfRecord at the same level as if // the GUID were part of MemProfRecord. template <> struct MappingTraits { // Helper class to normalize CalleeGuids to use GUIDHex64 for YAML I/O. class CallSiteInfoWithHex64Guids { public: CallSiteInfoWithHex64Guids(IO &) {} CallSiteInfoWithHex64Guids(IO &, const memprof::CallSiteInfo &CS) : Frames(CS.Frames) { // Convert uint64_t GUIDs to GUIDHex64 for serialization. CalleeGuids.reserve(CS.CalleeGuids.size()); for (uint64_t Guid : CS.CalleeGuids) CalleeGuids.push_back(memprof::GUIDHex64(Guid)); } memprof::CallSiteInfo denormalize(IO &) { memprof::CallSiteInfo CS; CS.Frames = Frames; // Convert GUIDHex64 back to uint64_t GUIDs after deserialization. CS.CalleeGuids.reserve(CalleeGuids.size()); for (memprof::GUIDHex64 HexGuid : CalleeGuids) CS.CalleeGuids.push_back(HexGuid.value); return CS; } // Keep Frames as is, since MappingTraits handles its // Function GUID. decltype(memprof::CallSiteInfo::Frames) Frames; // Use a vector of GUIDHex64 for CalleeGuids to leverage its ScalarTraits. SmallVector CalleeGuids; }; static void mapping(IO &Io, memprof::CallSiteInfo &CS) { // Use MappingNormalization to handle the conversion between // memprof::CallSiteInfo and CallSiteInfoWithHex64Guids. MappingNormalization Keys(Io, CS); Io.mapRequired("Frames", Keys->Frames); // Map the normalized CalleeGuids (which are now GUIDHex64). Io.mapOptional("CalleeGuids", Keys->CalleeGuids); } }; template <> struct MappingTraits { static void mapping(IO &Io, memprof::GUIDMemProfRecordPair &Pair) { Io.mapRequired("GUID", Pair.GUID); Io.mapOptional("AllocSites", Pair.Record.AllocSites); Io.mapOptional("CallSites", Pair.Record.CallSites); } }; template <> struct MappingTraits { static void mapping(IO &Io, memprof::SourceLocation &Loc) { Io.mapOptional("FileName", Loc.FileName); Io.mapOptional("Line", Loc.Line); } }; template <> struct MappingTraits { static void mapping(IO &Io, memprof::DataAccessProfRecord &Rec) { if (Io.outputting()) { if (std::holds_alternative(Rec.SymHandle)) { Io.mapOptional("Symbol", std::get(Rec.SymHandle)); } else { Io.mapOptional("Hash", std::get(Rec.SymHandle)); } } else { std::string SymName; uint64_t Hash = 0; Io.mapOptional("Symbol", SymName); Io.mapOptional("Hash", Hash); if (!SymName.empty()) { Rec.SymHandle = SymName; } else { Rec.SymHandle = Hash; } } Io.mapRequired("AccessCount", Rec.AccessCount); Io.mapOptional("Locations", Rec.Locations); } }; template <> struct MappingTraits { static void mapping(IO &Io, memprof::YamlDataAccessProfData &Data) { Io.mapOptional("SampledRecords", Data.Records); Io.mapOptional("KnownColdSymbols", Data.KnownColdSymbols); Io.mapOptional("KnownColdStrHashes", Data.KnownColdStrHashes); } }; template <> struct MappingTraits { static void mapping(IO &Io, memprof::AllMemProfData &Data) { if (!Io.outputting() || !Data.HeapProfileRecords.empty()) Io.mapOptional("HeapProfileRecords", Data.HeapProfileRecords); // Map data access profiles if reading input, or if writing output && // the struct is populated. if (!Io.outputting() || !Data.YamlifiedDataAccessProfiles.isEmpty()) Io.mapOptional("DataAccessProfiles", Data.YamlifiedDataAccessProfiles); } }; template <> struct SequenceTraits> { static size_t size(IO &io, SmallVector &Seq) { return Seq.size(); } static memprof::GUIDHex64 & element(IO &io, SmallVector &Seq, size_t Index) { if (Index >= Seq.size()) Seq.resize(Index + 1); return Seq[Index]; } static const bool flow = true; }; } // namespace yaml } // namespace llvm LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::Frame) LLVM_YAML_IS_SEQUENCE_VECTOR(std::vector) LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::AllocationInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::CallSiteInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::GUIDMemProfRecordPair) LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::GUIDHex64) // Used for CalleeGuids LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::DataAccessProfRecord) LLVM_YAML_IS_SEQUENCE_VECTOR(memprof::SourceLocation) #endif // LLVM_PROFILEDATA_MEMPROFYAML_H_