Skip to content

Commit 105adf2

Browse files
authored
[SHT_LLVM_BB_ADDR_MAP] Implements PGOAnalysisMap in Object and ObjectYAML with tests.
Reviewed in PR (#71750). A part of [RFC - PGO Accuracy Metrics: Emitting and Evaluating Branch and Block Analysis](https://discourse.llvm.org/t/rfc-pgo-accuracy-metrics-emitting-and-evaluating-branch-and-block-analysis/73902). This PR adds the PGOAnalysisMap data structure and implements encoding and decoding through Object and ObjectYAML along with associated tests. When emitted into the bb-addr-map section, each function is followed by the associated pgo-analysis-map for that function. The emitting of each analysis in the map is controlled by a bit in the bb-addr-map feature byte. All existing bb-addr-map code can ignore the pgo-analysis-map if the caller does not request the data.
1 parent ef35da8 commit 105adf2

File tree

10 files changed

+752
-57
lines changed

10 files changed

+752
-57
lines changed

llvm/include/llvm/Object/ELF.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,12 @@ class ELFFile {
457457
/// within the text section that the SHT_LLVM_BB_ADDR_MAP section \p Sec
458458
/// is associated with. If the current ELFFile is relocatable, a corresponding
459459
/// \p RelaSec must be passed in as an argument.
460+
/// Optional out variable to collect all PGO Analyses. New elements are only
461+
/// added if no error occurs. If not provided, the PGO Analyses are decoded
462+
/// then ignored.
460463
Expected<std::vector<BBAddrMap>>
461-
decodeBBAddrMap(const Elf_Shdr &Sec, const Elf_Shdr *RelaSec = nullptr) const;
464+
decodeBBAddrMap(const Elf_Shdr &Sec, const Elf_Shdr *RelaSec = nullptr,
465+
std::vector<PGOAnalysisMap> *PGOAnalyses = nullptr) const;
462466

463467
/// Returns a map from every section matching \p IsMatch to its relocation
464468
/// section, or \p nullptr if it has no relocation section. This function

llvm/include/llvm/Object/ELFObjectFile.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,13 @@ class ELFObjectFileBase : public ObjectFile {
110110

111111
/// Returns a vector of all BB address maps in the object file. When
112112
// `TextSectionIndex` is specified, only returns the BB address maps
113-
// corresponding to the section with that index.
113+
// corresponding to the section with that index. When `PGOAnalyses`is
114+
// specified, the vector is cleared then filled with extra PGO data.
115+
// `PGOAnalyses` will always be the same length as the return value on
116+
// success, otherwise it is empty.
114117
Expected<std::vector<BBAddrMap>>
115-
readBBAddrMap(std::optional<unsigned> TextSectionIndex = std::nullopt) const;
118+
readBBAddrMap(std::optional<unsigned> TextSectionIndex = std::nullopt,
119+
std::vector<PGOAnalysisMap> *PGOAnalyses = nullptr) const;
116120
};
117121

118122
class ELFSectionRef : public SectionRef {

llvm/include/llvm/Object/ELFTypes.h

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "llvm/ADT/StringRef.h"
1414
#include "llvm/BinaryFormat/ELF.h"
1515
#include "llvm/Object/Error.h"
16+
#include "llvm/Support/BlockFrequency.h"
17+
#include "llvm/Support/BranchProbability.h"
1618
#include "llvm/Support/Endian.h"
1719
#include "llvm/Support/Error.h"
1820
#include "llvm/Support/MathExtras.h"
@@ -875,6 +877,79 @@ struct BBAddrMap {
875877
std::vector<BBEntry> BBEntries; // Basic block entries for this function.
876878
};
877879

880+
/// A feature extension of BBAddrMap that holds information relevant to PGO.
881+
struct PGOAnalysisMap {
882+
/// Bitfield of optional features to include in the PGO extended map.
883+
struct Features {
884+
bool FuncEntryCount : 1;
885+
bool BBFreq : 1;
886+
bool BrProb : 1;
887+
888+
// Encodes to minimum bit width representation.
889+
uint8_t encode() const {
890+
return (static_cast<uint8_t>(FuncEntryCount) << 0) |
891+
(static_cast<uint8_t>(BBFreq) << 1) |
892+
(static_cast<uint8_t>(BrProb) << 2);
893+
}
894+
895+
// Decodes from minimum bit width representation and validates no
896+
// unnecessary bits are used.
897+
static Expected<Features> decode(uint8_t Val) {
898+
Features Feat{static_cast<bool>(Val & (1 << 0)),
899+
static_cast<bool>(Val & (1 << 1)),
900+
static_cast<bool>(Val & (1 << 2))};
901+
if (Feat.encode() != Val)
902+
return createStringError(
903+
std::error_code(),
904+
"invalid encoding for PGOAnalysisMap::Features: 0x%x", Val);
905+
return Feat;
906+
}
907+
908+
bool operator==(const Features &Other) const {
909+
return std::tie(FuncEntryCount, BBFreq, BrProb) ==
910+
std::tie(Other.FuncEntryCount, Other.BBFreq, Other.BrProb);
911+
}
912+
};
913+
914+
/// Extra basic block data with fields for block frequency and branch
915+
/// probability.
916+
struct PGOBBEntry {
917+
/// Single successor of a given basic block that contains the tag and branch
918+
/// probability associated with it.
919+
struct SuccessorEntry {
920+
/// Unique ID of this successor basic block.
921+
uint32_t ID;
922+
/// Branch Probability of the edge to this successor taken from MBPI.
923+
BranchProbability Prob;
924+
925+
bool operator==(const SuccessorEntry &Other) const {
926+
return std::tie(ID, Prob) == std::tie(Other.ID, Other.Prob);
927+
}
928+
};
929+
930+
/// Block frequency taken from MBFI
931+
BlockFrequency BlockFreq;
932+
/// List of successors of the current block
933+
llvm::SmallVector<SuccessorEntry, 2> Successors;
934+
935+
bool operator==(const PGOBBEntry &Other) const {
936+
return std::tie(BlockFreq, Successors) ==
937+
std::tie(Other.BlockFreq, Other.Successors);
938+
}
939+
};
940+
941+
uint64_t FuncEntryCount; // Prof count from IR function
942+
std::vector<PGOBBEntry> BBEntries; // Extended basic block entries
943+
944+
// Flags to indicate if each PGO related info was enabled in this function
945+
Features FeatEnable;
946+
947+
bool operator==(const PGOAnalysisMap &Other) const {
948+
return std::tie(FuncEntryCount, BBEntries, FeatEnable) ==
949+
std::tie(Other.FuncEntryCount, Other.BBEntries, Other.FeatEnable);
950+
}
951+
};
952+
878953
} // end namespace object.
879954
} // end namespace llvm.
880955

llvm/include/llvm/ObjectYAML/ELFYAML.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,19 @@ struct BBAddrMapEntry {
170170
std::optional<std::vector<BBEntry>> BBEntries;
171171
};
172172

173+
struct PGOAnalysisMapEntry {
174+
struct PGOBBEntry {
175+
struct SuccessorEntry {
176+
uint32_t ID;
177+
llvm::yaml::Hex32 BrProb;
178+
};
179+
std::optional<uint64_t> BBFreq;
180+
std::optional<std::vector<SuccessorEntry>> Successors;
181+
};
182+
std::optional<uint64_t> FuncEntryCount;
183+
std::optional<std::vector<PGOBBEntry>> PGOBBEntries;
184+
};
185+
173186
struct StackSizeEntry {
174187
llvm::yaml::Hex64 Address;
175188
llvm::yaml::Hex64 Size;
@@ -317,6 +330,7 @@ struct SectionHeaderTable : Chunk {
317330

318331
struct BBAddrMapSection : Section {
319332
std::optional<std::vector<BBAddrMapEntry>> Entries;
333+
std::optional<std::vector<PGOAnalysisMapEntry>> PGOAnalyses;
320334

321335
BBAddrMapSection() : Section(ChunkKind::BBAddrMap) {}
322336

@@ -737,6 +751,10 @@ bool shouldAllocateFileSpace(ArrayRef<ProgramHeader> Phdrs,
737751
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::StackSizeEntry)
738752
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::BBAddrMapEntry)
739753
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::BBAddrMapEntry::BBEntry)
754+
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::PGOAnalysisMapEntry)
755+
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::PGOAnalysisMapEntry::PGOBBEntry)
756+
LLVM_YAML_IS_SEQUENCE_VECTOR(
757+
llvm::ELFYAML::PGOAnalysisMapEntry::PGOBBEntry::SuccessorEntry)
740758
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::DynamicEntry)
741759
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::LinkerOption)
742760
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::CallGraphEntryWeight)
@@ -905,6 +923,21 @@ template <> struct MappingTraits<ELFYAML::BBAddrMapEntry::BBEntry> {
905923
static void mapping(IO &IO, ELFYAML::BBAddrMapEntry::BBEntry &Rel);
906924
};
907925

926+
template <> struct MappingTraits<ELFYAML::PGOAnalysisMapEntry> {
927+
static void mapping(IO &IO, ELFYAML::PGOAnalysisMapEntry &Rel);
928+
};
929+
930+
template <> struct MappingTraits<ELFYAML::PGOAnalysisMapEntry::PGOBBEntry> {
931+
static void mapping(IO &IO, ELFYAML::PGOAnalysisMapEntry::PGOBBEntry &Rel);
932+
};
933+
934+
template <>
935+
struct MappingTraits<ELFYAML::PGOAnalysisMapEntry::PGOBBEntry::SuccessorEntry> {
936+
static void
937+
mapping(IO &IO,
938+
ELFYAML::PGOAnalysisMapEntry::PGOBBEntry::SuccessorEntry &Rel);
939+
};
940+
908941
template <> struct MappingTraits<ELFYAML::GnuHashHeader> {
909942
static void mapping(IO &IO, ELFYAML::GnuHashHeader &Rel);
910943
};

llvm/lib/Object/ELF.cpp

Lines changed: 112 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -646,11 +646,36 @@ ELFFile<ELFT>::toMappedAddr(uint64_t VAddr, WarningHandler WarnHandler) const {
646646
return base() + Offset;
647647
}
648648

649-
template <class ELFT>
650-
Expected<std::vector<BBAddrMap>>
651-
ELFFile<ELFT>::decodeBBAddrMap(const Elf_Shdr &Sec,
652-
const Elf_Shdr *RelaSec) const {
653-
bool IsRelocatable = getHeader().e_type == ELF::ET_REL;
649+
// Helper to extract and decode the next ULEB128 value as unsigned int.
650+
// Returns zero and sets ULEBSizeErr if the ULEB128 value exceeds the unsigned
651+
// int limit.
652+
// Also returns zero if ULEBSizeErr is already in an error state.
653+
// ULEBSizeErr is an out variable if an error occurs.
654+
template <typename IntTy, std::enable_if_t<std::is_unsigned_v<IntTy>, int> = 0>
655+
static IntTy readULEB128As(DataExtractor &Data, DataExtractor::Cursor &Cur,
656+
Error &ULEBSizeErr) {
657+
// Bail out and do not extract data if ULEBSizeErr is already set.
658+
if (ULEBSizeErr)
659+
return 0;
660+
uint64_t Offset = Cur.tell();
661+
uint64_t Value = Data.getULEB128(Cur);
662+
if (Value > std::numeric_limits<IntTy>::max()) {
663+
ULEBSizeErr = createError("ULEB128 value at offset 0x" +
664+
Twine::utohexstr(Offset) + " exceeds UINT" +
665+
Twine(std::numeric_limits<IntTy>::digits) +
666+
"_MAX (0x" + Twine::utohexstr(Value) + ")");
667+
return 0;
668+
}
669+
return static_cast<IntTy>(Value);
670+
}
671+
672+
template <typename ELFT>
673+
static Expected<std::vector<BBAddrMap>>
674+
decodeBBAddrMapImpl(const ELFFile<ELFT> &EF,
675+
const typename ELFFile<ELFT>::Elf_Shdr &Sec,
676+
const typename ELFFile<ELFT>::Elf_Shdr *RelaSec,
677+
std::vector<PGOAnalysisMap> *PGOAnalyses) {
678+
bool IsRelocatable = EF.getHeader().e_type == ELF::ET_REL;
654679

655680
// This DenseMap maps the offset of each function (the location of the
656681
// reference to the function in the SHT_LLVM_BB_ADDR_MAP section) to the
@@ -660,44 +685,28 @@ ELFFile<ELFT>::decodeBBAddrMap(const Elf_Shdr &Sec,
660685
assert(RelaSec &&
661686
"Can't read a SHT_LLVM_BB_ADDR_MAP section in a relocatable "
662687
"object file without providing a relocation section.");
663-
Expected<Elf_Rela_Range> Relas = this->relas(*RelaSec);
688+
Expected<typename ELFFile<ELFT>::Elf_Rela_Range> Relas = EF.relas(*RelaSec);
664689
if (!Relas)
665690
return createError("unable to read relocations for section " +
666-
describe(*this, Sec) + ": " +
691+
describe(EF, Sec) + ": " +
667692
toString(Relas.takeError()));
668-
for (Elf_Rela Rela : *Relas)
693+
for (typename ELFFile<ELFT>::Elf_Rela Rela : *Relas)
669694
FunctionOffsetTranslations[Rela.r_offset] = Rela.r_addend;
670695
}
671-
Expected<ArrayRef<uint8_t>> ContentsOrErr = getSectionContents(Sec);
696+
Expected<ArrayRef<uint8_t>> ContentsOrErr = EF.getSectionContents(Sec);
672697
if (!ContentsOrErr)
673698
return ContentsOrErr.takeError();
674699
ArrayRef<uint8_t> Content = *ContentsOrErr;
675-
DataExtractor Data(Content, isLE(), ELFT::Is64Bits ? 8 : 4);
700+
DataExtractor Data(Content, EF.isLE(), ELFT::Is64Bits ? 8 : 4);
676701
std::vector<BBAddrMap> FunctionEntries;
677702

678703
DataExtractor::Cursor Cur(0);
679704
Error ULEBSizeErr = Error::success();
680705
Error MetadataDecodeErr = Error::success();
681-
// Helper to extract and decode the next ULEB128 value as uint32_t.
682-
// Returns zero and sets ULEBSizeErr if the ULEB128 value exceeds the uint32_t
683-
// limit.
684-
// Also returns zero if ULEBSizeErr is already in an error state.
685-
auto ReadULEB128AsUInt32 = [&Data, &Cur, &ULEBSizeErr]() -> uint32_t {
686-
// Bail out and do not extract data if ULEBSizeErr is already set.
687-
if (ULEBSizeErr)
688-
return 0;
689-
uint64_t Offset = Cur.tell();
690-
uint64_t Value = Data.getULEB128(Cur);
691-
if (Value > UINT32_MAX) {
692-
ULEBSizeErr = createError(
693-
"ULEB128 value at offset 0x" + Twine::utohexstr(Offset) +
694-
" exceeds UINT32_MAX (0x" + Twine::utohexstr(Value) + ")");
695-
return 0;
696-
}
697-
return static_cast<uint32_t>(Value);
698-
};
699706

700707
uint8_t Version = 0;
708+
uint8_t Feature = 0;
709+
PGOAnalysisMap::Features FeatEnable{};
701710
while (!ULEBSizeErr && !MetadataDecodeErr && Cur &&
702711
Cur.tell() < Content.size()) {
703712
if (Sec.sh_type == ELF::SHT_LLVM_BB_ADDR_MAP) {
@@ -707,10 +716,24 @@ ELFFile<ELFT>::decodeBBAddrMap(const Elf_Shdr &Sec,
707716
if (Version > 2)
708717
return createError("unsupported SHT_LLVM_BB_ADDR_MAP version: " +
709718
Twine(static_cast<int>(Version)));
710-
Data.getU8(Cur); // Feature byte
719+
Feature = Data.getU8(Cur); // Feature byte
720+
if (!Cur)
721+
break;
722+
auto FeatEnableOrErr = PGOAnalysisMap::Features::decode(Feature);
723+
if (!FeatEnableOrErr)
724+
return FeatEnableOrErr.takeError();
725+
FeatEnable =
726+
FeatEnableOrErr ? *FeatEnableOrErr : PGOAnalysisMap::Features{};
727+
if (Feature != 0 && Version < 2 && Cur)
728+
return createError(
729+
"version should be >= 2 for SHT_LLVM_BB_ADDR_MAP when "
730+
"PGO features are enabled: version = " +
731+
Twine(static_cast<int>(Version)) +
732+
" feature = " + Twine(static_cast<int>(Feature)));
711733
}
712734
uint64_t SectionOffset = Cur.tell();
713-
uintX_t Address = static_cast<uintX_t>(Data.getAddress(Cur));
735+
auto Address =
736+
static_cast<typename ELFFile<ELFT>::uintX_t>(Data.getAddress(Cur));
714737
if (!Cur)
715738
return Cur.takeError();
716739
if (IsRelocatable) {
@@ -719,20 +742,23 @@ ELFFile<ELFT>::decodeBBAddrMap(const Elf_Shdr &Sec,
719742
if (FOTIterator == FunctionOffsetTranslations.end()) {
720743
return createError("failed to get relocation data for offset: " +
721744
Twine::utohexstr(SectionOffset) + " in section " +
722-
describe(*this, Sec));
745+
describe(EF, Sec));
723746
}
724747
Address = FOTIterator->second;
725748
}
726-
uint32_t NumBlocks = ReadULEB128AsUInt32();
749+
uint32_t NumBlocks = readULEB128As<uint32_t>(Data, Cur, ULEBSizeErr);
750+
727751
std::vector<BBAddrMap::BBEntry> BBEntries;
728752
uint32_t PrevBBEndOffset = 0;
729753
for (uint32_t BlockIndex = 0;
730754
!MetadataDecodeErr && !ULEBSizeErr && Cur && (BlockIndex < NumBlocks);
731755
++BlockIndex) {
732-
uint32_t ID = Version >= 2 ? ReadULEB128AsUInt32() : BlockIndex;
733-
uint32_t Offset = ReadULEB128AsUInt32();
734-
uint32_t Size = ReadULEB128AsUInt32();
735-
uint32_t MD = ReadULEB128AsUInt32();
756+
uint32_t ID = Version >= 2
757+
? readULEB128As<uint32_t>(Data, Cur, ULEBSizeErr)
758+
: BlockIndex;
759+
uint32_t Offset = readULEB128As<uint32_t>(Data, Cur, ULEBSizeErr);
760+
uint32_t Size = readULEB128As<uint32_t>(Data, Cur, ULEBSizeErr);
761+
uint32_t MD = readULEB128As<uint32_t>(Data, Cur, ULEBSizeErr);
736762
if (Version >= 1) {
737763
// Offset is calculated relative to the end of the previous BB.
738764
Offset += PrevBBEndOffset;
@@ -747,6 +773,44 @@ ELFFile<ELFT>::decodeBBAddrMap(const Elf_Shdr &Sec,
747773
BBEntries.push_back({ID, Offset, Size, *MetadataOrErr});
748774
}
749775
FunctionEntries.emplace_back(Address, std::move(BBEntries));
776+
777+
if (FeatEnable.FuncEntryCount || FeatEnable.BBFreq || FeatEnable.BrProb) {
778+
// Function entry count
779+
uint64_t FuncEntryCount =
780+
FeatEnable.FuncEntryCount
781+
? readULEB128As<uint64_t>(Data, Cur, ULEBSizeErr)
782+
: 0;
783+
784+
std::vector<PGOAnalysisMap::PGOBBEntry> PGOBBEntries;
785+
for (uint32_t BlockIndex = 0; !MetadataDecodeErr && !ULEBSizeErr && Cur &&
786+
(BlockIndex < NumBlocks);
787+
++BlockIndex) {
788+
// Block frequency
789+
uint64_t BBF = FeatEnable.BBFreq
790+
? readULEB128As<uint64_t>(Data, Cur, ULEBSizeErr)
791+
: 0;
792+
793+
// Branch probability
794+
llvm::SmallVector<PGOAnalysisMap::PGOBBEntry::SuccessorEntry, 2>
795+
Successors;
796+
if (FeatEnable.BrProb) {
797+
auto SuccCount = readULEB128As<uint64_t>(Data, Cur, ULEBSizeErr);
798+
for (uint64_t I = 0; I < SuccCount; ++I) {
799+
uint32_t BBID = readULEB128As<uint32_t>(Data, Cur, ULEBSizeErr);
800+
uint32_t BrProb = readULEB128As<uint32_t>(Data, Cur, ULEBSizeErr);
801+
if (PGOAnalyses)
802+
Successors.push_back({BBID, BranchProbability::getRaw(BrProb)});
803+
}
804+
}
805+
806+
if (PGOAnalyses)
807+
PGOBBEntries.push_back({BlockFrequency(BBF), std::move(Successors)});
808+
}
809+
810+
if (PGOAnalyses)
811+
PGOAnalyses->push_back(
812+
{FuncEntryCount, std::move(PGOBBEntries), FeatEnable});
813+
}
750814
}
751815
// Either Cur is in the error state, or we have an error in ULEBSizeErr or
752816
// MetadataDecodeErr (but not both), but we join all errors here to be safe.
@@ -756,6 +820,18 @@ ELFFile<ELFT>::decodeBBAddrMap(const Elf_Shdr &Sec,
756820
return FunctionEntries;
757821
}
758822

823+
template <class ELFT>
824+
Expected<std::vector<BBAddrMap>>
825+
ELFFile<ELFT>::decodeBBAddrMap(const Elf_Shdr &Sec, const Elf_Shdr *RelaSec,
826+
std::vector<PGOAnalysisMap> *PGOAnalyses) const {
827+
size_t OriginalPGOSize = PGOAnalyses ? PGOAnalyses->size() : 0;
828+
auto AddrMapsOrErr = decodeBBAddrMapImpl(*this, Sec, RelaSec, PGOAnalyses);
829+
// remove new analyses when an error occurs
830+
if (!AddrMapsOrErr && PGOAnalyses)
831+
PGOAnalyses->resize(OriginalPGOSize);
832+
return std::move(AddrMapsOrErr);
833+
}
834+
759835
template <class ELFT>
760836
Expected<
761837
MapVector<const typename ELFT::Shdr *, const typename ELFT::Shdr *>>

0 commit comments

Comments
 (0)