Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions llvm/include/llvm/ProfileData/DataAccessProf.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ namespace data_access_prof {
struct SourceLocation {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Re: line +38]

Should we change this to memprof? Reasoning is that this is closely tied to the memprof profile data format and we don't intend to use it elsewhere. Wdyt?

See this comment inline on Graphite.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense. Renamed namespace in this patch and I'm open to split renames to a separate patch before this one.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with keeping the rename in this patch.

SourceLocation(StringRef FileNameRef, uint32_t Line)
: FileName(FileNameRef.str()), Line(Line) {}

SourceLocation() {}
/// The filename where the data is located.
std::string FileName;
/// The line number in the source code.
Expand All @@ -53,6 +55,8 @@ namespace internal {
// which strings are owned by `DataAccessProfData`. Used by `DataAccessProfData`
// to represent data locations internally.
struct SourceLocationRef {
SourceLocationRef(StringRef FileNameRef, uint32_t Line)
: FileName(FileNameRef), Line(Line) {}
// The filename where the data is located.
StringRef FileName;
// The line number in the source code.
Expand Down Expand Up @@ -100,8 +104,9 @@ using SymbolHandle = std::variant<std::string, uint64_t>;
/// The data access profiles for a symbol.
struct DataAccessProfRecord {
public:
DataAccessProfRecord(SymbolHandleRef SymHandleRef,
ArrayRef<internal::SourceLocationRef> LocRefs) {
DataAccessProfRecord(SymbolHandleRef SymHandleRef, uint64_t AccessCount,
ArrayRef<internal::SourceLocationRef> LocRefs)
: AccessCount(AccessCount) {
if (std::holds_alternative<StringRef>(SymHandleRef)) {
SymHandle = std::get<StringRef>(SymHandleRef).str();
} else
Expand All @@ -110,8 +115,9 @@ struct DataAccessProfRecord {
for (auto Loc : LocRefs)
Locations.push_back(SourceLocation(Loc.FileName, Loc.Line));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emplace back to avoid a copy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

}
DataAccessProfRecord() {}
SymbolHandle SymHandle;

uint64_t AccessCount;
// The locations of data in the source code. Optional.
SmallVector<SourceLocation> Locations;
};
Expand Down
12 changes: 9 additions & 3 deletions llvm/include/llvm/ProfileData/IndexedMemProfData.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@
//
//===----------------------------------------------------------------------===//

#include "llvm/ProfileData/DataAccessProf.h"
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/ProfileData/MemProf.h"

#include <functional>
#include <optional>

namespace llvm {

// Write the MemProf data to OS.
Error writeMemProf(ProfOStream &OS, memprof::IndexedMemProfData &MemProfData,
memprof::IndexedVersion MemProfVersionRequested,
bool MemProfFullSchema);
Error writeMemProf(
ProfOStream &OS, memprof::IndexedMemProfData &MemProfData,
memprof::IndexedVersion MemProfVersionRequested, bool MemProfFullSchema,
std::optional<std::reference_wrapper<data_access_prof::DataAccessProfData>>
DataAccessProfileData);

} // namespace llvm
6 changes: 5 additions & 1 deletion llvm/include/llvm/ProfileData/InstrProfReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/ProfileSummary.h"
#include "llvm/Object/BuildID.h"
#include "llvm/ProfileData/DataAccessProf.h"
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/ProfileData/InstrProfCorrelator.h"
#include "llvm/ProfileData/MemProf.h"
Expand Down Expand Up @@ -703,10 +704,13 @@ class IndexedMemProfReader {
const unsigned char *CallStackBase = nullptr;
// The number of elements in the radix tree array.
unsigned RadixTreeSize = 0;
/// The data access profiles, deserialized from binary data.
std::unique_ptr<data_access_prof::DataAccessProfData> DataAccessProfileData;

Error deserializeV2(const unsigned char *Start, const unsigned char *Ptr);
Error deserializeRadixTreeBased(const unsigned char *Start,
const unsigned char *Ptr);
const unsigned char *Ptr,
memprof::IndexedVersion Version);

public:
IndexedMemProfReader() = default;
Expand Down
6 changes: 6 additions & 0 deletions llvm/include/llvm/ProfileData/InstrProfWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "llvm/ADT/StringMap.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/Object/BuildID.h"
#include "llvm/ProfileData/DataAccessProf.h"
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/ProfileData/MemProf.h"
#include "llvm/Support/Error.h"
Expand Down Expand Up @@ -81,6 +82,8 @@ class InstrProfWriter {
// Whether to generated random memprof hotness for testing.
bool MemprofGenerateRandomHotness;

std::unique_ptr<data_access_prof::DataAccessProfData> DataAccessProfileData;

public:
// For memprof testing, random hotness can be assigned to the contexts if
// MemprofGenerateRandomHotness is enabled. The random seed can be either
Expand Down Expand Up @@ -122,6 +125,9 @@ class InstrProfWriter {
// Add a binary id to the binary ids list.
void addBinaryIds(ArrayRef<llvm::object::BuildID> BIs);

void addDataAccessProfData(
std::unique_ptr<data_access_prof::DataAccessProfData> DataAccessProfile);

/// Merge existing function counts from the given writer.
void mergeRecordsFromWriter(InstrProfWriter &&IPW,
function_ref<void(Error)> Warn);
Expand Down
15 changes: 15 additions & 0 deletions llvm/include/llvm/ProfileData/MemProfReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,21 @@ class YAMLMemProfReader final : public MemProfReader {
create(std::unique_ptr<MemoryBuffer> Buffer);

void parse(StringRef YAMLData);

std::unique_ptr<data_access_prof::DataAccessProfData>
takeDataAccessProfData() {
return std::move(DataAccessProfileData);
}

private:
// Called by `parse` to set data access profiles after parsing them from Yaml
// files.
void setDataAccessProfileData(
std::unique_ptr<data_access_prof::DataAccessProfData> Data) {
DataAccessProfileData = std::move(Data);
}

std::unique_ptr<data_access_prof::DataAccessProfData> DataAccessProfileData;
};
} // namespace memprof
} // namespace llvm
Expand Down
61 changes: 61 additions & 0 deletions llvm/include/llvm/ProfileData/MemProfYAML.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#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"
Expand All @@ -20,9 +21,24 @@ struct GUIDMemProfRecordPair {
MemProfRecord Record;
};

// Helper struct to yamlify data_access_prof::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<data_access_prof::DataAccessProfRecord> Records;
std::vector<uint64_t> KnownColdHashes;
std::vector<std::string> KnownColdSymbols;

bool isEmpty() const {
return Records.empty() && KnownColdHashes.empty() &&
KnownColdSymbols.empty();
}
};

// The top-level data structure, only used with YAML for now.
struct AllMemProfData {
std::vector<GUIDMemProfRecordPair> HeapProfileRecords;
YamlDataAccessProfData YamlifiedDataAccessProfiles;
};
} // namespace memprof

Expand Down Expand Up @@ -206,9 +222,52 @@ template <> struct MappingTraits<memprof::GUIDMemProfRecordPair> {
}
};

template <> struct MappingTraits<data_access_prof::SourceLocation> {
static void mapping(IO &Io, data_access_prof::SourceLocation &Loc) {
Io.mapOptional("FileName", Loc.FileName);
Io.mapOptional("Line", Loc.Line);
}
};

template <> struct MappingTraits<data_access_prof::DataAccessProfRecord> {
static void mapping(IO &Io, data_access_prof::DataAccessProfRecord &Rec) {
if (Io.outputting()) {
if (std::holds_alternative<std::string>(Rec.SymHandle)) {
Io.mapOptional("Symbol", std::get<std::string>(Rec.SymHandle));
} else {
Io.mapOptional("Hash", std::get<uint64_t>(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.mapOptional("Locations", Rec.Locations);
}
};

template <> struct MappingTraits<memprof::YamlDataAccessProfData> {
static void mapping(IO &Io, memprof::YamlDataAccessProfData &Data) {
Io.mapOptional("SampledRecords", Data.Records);
Io.mapOptional("KnownColdSymbols", Data.KnownColdSymbols);
Io.mapOptional("KnownColdHashes", Data.KnownColdHashes);
}
};

template <> struct MappingTraits<memprof::AllMemProfData> {
static void mapping(IO &Io, memprof::AllMemProfData &Data) {
Io.mapRequired("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);
}
};

Expand All @@ -234,5 +293,7 @@ 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(data_access_prof::DataAccessProfRecord)
LLVM_YAML_IS_SEQUENCE_VECTOR(data_access_prof::SourceLocation)

#endif // LLVM_PROFILEDATA_MEMPROFYAML_H_
3 changes: 2 additions & 1 deletion llvm/lib/ProfileData/DataAccessProf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ DataAccessProfData::getProfileRecord(const SymbolHandleRef SymbolID) const {

auto It = Records.find(Key);
if (It != Records.end()) {
return DataAccessProfRecord(Key, It->second.Locations);
return DataAccessProfRecord(Key, It->second.AccessCount,
It->second.Locations);
}

return std::nullopt;
Expand Down
63 changes: 50 additions & 13 deletions llvm/lib/ProfileData/IndexedMemProfData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//

#include "llvm/ProfileData/DataAccessProf.h"
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/ProfileData/InstrProfReader.h"
#include "llvm/ProfileData/MemProf.h"
Expand Down Expand Up @@ -216,7 +217,9 @@ static Error writeMemProfV2(ProfOStream &OS,

static Error writeMemProfRadixTreeBased(
ProfOStream &OS, memprof::IndexedMemProfData &MemProfData,
memprof::IndexedVersion Version, bool MemProfFullSchema) {
memprof::IndexedVersion Version, bool MemProfFullSchema,
std::optional<std::reference_wrapper<data_access_prof::DataAccessProfData>>
DataAccessProfileData) {
assert((Version == memprof::Version3 || Version == memprof::Version4) &&
"Unsupported version for radix tree format");

Expand All @@ -225,6 +228,8 @@ static Error writeMemProfRadixTreeBased(
OS.write(0ULL); // Reserve space for the memprof call stack payload offset.
OS.write(0ULL); // Reserve space for the memprof record payload offset.
OS.write(0ULL); // Reserve space for the memprof record table offset.
if (Version >= memprof::Version4)
OS.write(0ULL); // Reserve space for the data access profile offset.

auto Schema = memprof::getHotColdSchema();
if (MemProfFullSchema)
Expand All @@ -251,17 +256,28 @@ static Error writeMemProfRadixTreeBased(
uint64_t RecordTableOffset = writeMemProfRecords(
OS, MemProfData.Records, &Schema, Version, &MemProfCallStackIndexes);

uint64_t DataAccessProfOffset = 0;
if (DataAccessProfileData.has_value()) {
assert(Version >= memprof::Version4 &&
"Data access profiles are added starting from v4");
DataAccessProfOffset = OS.tell();
if (Error E = (*DataAccessProfileData).get().serialize(OS))
return E;
}

// Verify that the computation for the number of elements in the call stack
// array works.
assert(CallStackPayloadOffset +
NumElements * sizeof(memprof::LinearFrameId) ==
RecordPayloadOffset);

uint64_t Header[] = {
SmallVector<uint64_t, 4> Header = {
CallStackPayloadOffset,
RecordPayloadOffset,
RecordTableOffset,
};
if (Version >= memprof::Version4)
Header.push_back(DataAccessProfOffset);
OS.patch({{HeaderUpdatePos, Header}});

return Error::success();
Expand All @@ -272,28 +288,33 @@ static Error writeMemProfV3(ProfOStream &OS,
memprof::IndexedMemProfData &MemProfData,
bool MemProfFullSchema) {
return writeMemProfRadixTreeBased(OS, MemProfData, memprof::Version3,
MemProfFullSchema);
MemProfFullSchema, std::nullopt);
}

// Write out MemProf Version4
static Error writeMemProfV4(ProfOStream &OS,
memprof::IndexedMemProfData &MemProfData,
bool MemProfFullSchema) {
static Error writeMemProfV4(
ProfOStream &OS, memprof::IndexedMemProfData &MemProfData,
bool MemProfFullSchema,
std::optional<std::reference_wrapper<data_access_prof::DataAccessProfData>>
DataAccessProfileData) {
return writeMemProfRadixTreeBased(OS, MemProfData, memprof::Version4,
MemProfFullSchema);
MemProfFullSchema, DataAccessProfileData);
}

// Write out the MemProf data in a requested version.
Error writeMemProf(ProfOStream &OS, memprof::IndexedMemProfData &MemProfData,
memprof::IndexedVersion MemProfVersionRequested,
bool MemProfFullSchema) {
Error writeMemProf(
ProfOStream &OS, memprof::IndexedMemProfData &MemProfData,
memprof::IndexedVersion MemProfVersionRequested, bool MemProfFullSchema,
std::optional<std::reference_wrapper<data_access_prof::DataAccessProfData>>
DataAccessProfileData) {
switch (MemProfVersionRequested) {
case memprof::Version2:
return writeMemProfV2(OS, MemProfData, MemProfFullSchema);
case memprof::Version3:
return writeMemProfV3(OS, MemProfData, MemProfFullSchema);
case memprof::Version4:
return writeMemProfV4(OS, MemProfData, MemProfFullSchema);
return writeMemProfV4(OS, MemProfData, MemProfFullSchema,
DataAccessProfileData);
}

return make_error<InstrProfError>(
Expand Down Expand Up @@ -357,7 +378,10 @@ Error IndexedMemProfReader::deserializeV2(const unsigned char *Start,
}

Error IndexedMemProfReader::deserializeRadixTreeBased(
const unsigned char *Start, const unsigned char *Ptr) {
const unsigned char *Start, const unsigned char *Ptr,
memprof::IndexedVersion Version) {
assert((Version == memprof::Version3 || Version == memprof::Version4) &&
"Unsupported version for radix tree format");
// The offset in the stream right before invoking
// CallStackTableGenerator.Emit.
const uint64_t CallStackPayloadOffset =
Expand All @@ -369,6 +393,11 @@ Error IndexedMemProfReader::deserializeRadixTreeBased(
const uint64_t RecordTableOffset =
support::endian::readNext<uint64_t, llvm::endianness::little>(Ptr);

uint64_t DataAccessProfOffset = 0;
if (Version == memprof::Version4)
DataAccessProfOffset =
support::endian::readNext<uint64_t, llvm::endianness::little>(Ptr);

// Read the schema.
auto SchemaOr = memprof::readMemProfSchema(Ptr);
if (!SchemaOr)
Expand All @@ -390,6 +419,14 @@ Error IndexedMemProfReader::deserializeRadixTreeBased(
/*Payload=*/Start + RecordPayloadOffset,
/*Base=*/Start, memprof::RecordLookupTrait(Version, Schema)));

if (DataAccessProfOffset > RecordTableOffset) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to have an non-zero DataAccessProfOffset that is <= RecordTableOffset? Perhaps assert (!DataAccessProfOffset || ... > RecordTableOffset), and use if (DataAccessProfOffset !=0) as the guard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea to tighten the check resonates with me.

Maybe I can record both DAP offset and DAP length in the memprof header, and handle empty input in the deserialize method like the diff shows. How does this sound?


-Error DataAccessProfData::deserialize(const unsigned char *&Ptr) {
+Error DataAccessProfData::deserialize(const unsigned char *&Ptr, uint64_t Len) {
+  if (Len == 0)
+    return Error::success();

Copy link
Contributor Author

@mingmingl-llvm mingmingl-llvm May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized checking the length of data access profile payload before creating the class or calling its 'deserialize' method will do it, and there is no need to update the signature of DataAccessProfData::deserialize. Since it's towards the end of the working week and the change is ~20 lines , I took the liberty to go ahead :)

I'm open to make the length field more general (e.g., recording the length of the total memprof profile will make it useful when there are new payloads after data access profiles) and would like to hear thoughts on it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really follow the benefit of adding the length for this particular section? Not completely against it, but we don't seem to have the length for the other sections. And it isn't clear to me that guarding the deserialization by the length is inherently better than comparing the offsets. I do think that some assertion checking could be added in either case though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Un-do the length changes and added the assert((!DataAccessProfOffset || DataAccessProfOffset > RecordTableOffset) && "message") for simplicity.

Recording and checking length makes the code a little more straightforward and readable, but the implementation is a little more complex. I don't feel strong about it.

DataAccessProfileData =
std::make_unique<data_access_prof::DataAccessProfData>();
const unsigned char *DAPPtr = Start + DataAccessProfOffset;
if (Error E = DataAccessProfileData->deserialize(DAPPtr))
return E;
}

return Error::success();
}

Expand Down Expand Up @@ -423,7 +460,7 @@ Error IndexedMemProfReader::deserialize(const unsigned char *Start,
case memprof::Version3:
case memprof::Version4:
// V3 and V4 share the same high-level structure (radix tree, linear IDs).
if (Error E = deserializeRadixTreeBased(Start, Ptr))
if (Error E = deserializeRadixTreeBased(Start, Ptr, Version))
return E;
break;
}
Expand Down
Loading