//===- bolt/unittests/Profile/PerfSpeEvents.cpp ---------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifdef AARCH64_AVAILABLE #include "bolt/Core/BinaryContext.h" #include "bolt/Profile/DataAggregator.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/TargetSelect.h" #include "gtest/gtest.h" using namespace llvm; using namespace llvm::bolt; using namespace llvm::object; using namespace llvm::ELF; namespace opts { extern cl::opt ReadPerfEvents; extern cl::opt ArmSPE; } // namespace opts namespace llvm { namespace bolt { /// Perform checks on perf SPE branch events. struct PerfSpeEventsTestHelper : public testing::Test { void SetUp() override { initalizeLLVM(); prepareElf(); initializeBOLT(); } protected: using Trace = DataAggregator::Trace; using TakenBranchInfo = DataAggregator::TakenBranchInfo; void initalizeLLVM() { llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllAsmParsers(); llvm::InitializeAllDisassemblers(); llvm::InitializeAllTargets(); llvm::InitializeAllAsmPrinters(); } void prepareElf() { memcpy(ElfBuf, "\177ELF", 4); ELF64LE::Ehdr *EHdr = reinterpret_cast(ElfBuf); EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64; EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB; EHdr->e_machine = llvm::ELF::EM_AARCH64; MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF"); ObjFile = cantFail(ObjectFile::createObjectFile(Source)); } void initializeBOLT() { Relocation::Arch = ObjFile->makeTriple().getArch(); BC = cantFail(BinaryContext::createBinaryContext( ObjFile->makeTriple(), std::make_shared(), ObjFile->getFileName(), nullptr, /*IsPIC*/ false, DWARFContext::create(*ObjFile), {llvm::outs(), llvm::errs()})); ASSERT_FALSE(!BC); } char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {}; std::unique_ptr ObjFile; std::unique_ptr BC; /// Helper function to export lists to show the mismatch. void reportBrStackEventMismatch( const std::vector> &Traces, const std::vector> &ExpectedSamples) { llvm::errs() << "Traces items: \n"; for (const auto &[Trace, BI] : Traces) llvm::errs() << "{" << Trace.Branch << ", " << Trace.From << "," << Trace.To << ", " << BI.TakenCount << ", " << BI.MispredCount << "}" << "\n"; llvm::errs() << "Expected items: \n"; for (const auto &[Trace, BI] : ExpectedSamples) llvm::errs() << "{" << Trace.Branch << ", " << Trace.From << ", " << Trace.To << ", " << BI.TakenCount << ", " << BI.MispredCount << "}" << "\n"; } /// Parse and check SPE brstack as LBR. void parseAndCheckBrstackEvents( uint64_t PID, const std::vector> &ExpectedSamples) { DataAggregator DA(""); DA.ParsingBuf = opts::ReadPerfEvents; DA.BC = BC.get(); DataAggregator::MMapInfo MMap; DA.BinaryMMapInfo.insert(std::make_pair(PID, MMap)); DA.parseBranchEvents(); EXPECT_EQ(DA.Traces.size(), ExpectedSamples.size()); if (DA.Traces.size() != ExpectedSamples.size()) reportBrStackEventMismatch(DA.Traces, ExpectedSamples); const auto TracesBegin = DA.Traces.begin(); const auto TracesEnd = DA.Traces.end(); for (const auto &BI : ExpectedSamples) { auto it = find_if(TracesBegin, TracesEnd, [&BI](const auto &Tr) { return Tr.first == BI.first; }); EXPECT_NE(it, TracesEnd); EXPECT_EQ(it->second.MispredCount, BI.second.MispredCount); EXPECT_EQ(it->second.TakenCount, BI.second.TakenCount); } } }; } // namespace bolt } // namespace llvm TEST_F(PerfSpeEventsTestHelper, SpeBranchesWithBrstack) { // Check perf input with SPE branch events as brstack format. // Example collection command: // ``` // perf record -e 'arm_spe_0/branch_filter=1/u' -- BINARY // ``` // How Bolt extracts the branch events: // ``` // perf script -F pid,brstack --itrace=bl // ``` opts::ArmSPE = true; opts::ReadPerfEvents = " 1234 0xa001/0xa002/PN/-/-/10/COND/-\n" " 1234 0xb001/0xb002/P/-/-/4/RET/-\n" " 1234 0xc456/0xc789/P/-/-/13/-/-\n" " 1234 0xd123/0xd456/M/-/-/7/RET/-\n" " 1234 0xe001/0xe002/P/-/-/14/RET/-\n" " 1234 0xd123/0xd456/M/-/-/7/RET/-\n" " 1234 0xf001/0xf002/MN/-/-/8/COND/-\n" " 1234 0xc456/0xc789/M/-/-/13/-/-\n"; // ExpectedSamples contains the aggregated information about // a branch {{Branch From, To}, {TakenCount, MispredCount}}. // Consider this example trace: {{0xd123, 0xd456, Trace::BR_ONLY}, // {2,2}}. This entry has a TakenCount = 2, as we have two samples for // (0xd123, 0xd456) in our input. It also has MispredsCount = 2, // as 'M' misprediction flag appears in both cases. BR_ONLY means // the trace only contains branch data. std::vector> ExpectedSamples = { {{0xa001, 0xa002, Trace::BR_ONLY}, {1, 0}}, {{0xb001, 0xb002, Trace::BR_ONLY}, {1, 0}}, {{0xc456, 0xc789, Trace::BR_ONLY}, {2, 1}}, {{0xd123, 0xd456, Trace::BR_ONLY}, {2, 2}}, {{0xe001, 0xe002, Trace::BR_ONLY}, {1, 0}}, {{0xf001, 0xf002, Trace::BR_ONLY}, {1, 1}}}; parseAndCheckBrstackEvents(1234, ExpectedSamples); } TEST_F(PerfSpeEventsTestHelper, SpeBranchesWithBrstackAndPbt) { // Check perf input with SPE branch events as brstack format by // combining with the previous branch target address (named as PBT). // Example collection command: // ``` // perf record -e 'arm_spe_0/branch_filter=1/u' -- BINARY // ``` // How Bolt extracts the branch events: // ``` // perf script -F pid,brstack --itrace=bl // ``` opts::ArmSPE = true; opts::ReadPerfEvents = // " //PN/-/-/10/COND/- //-/-/-/0//-\n" " 4567 0xa002/0xa003/PN/-/-/10/COND/- 0x0/0xa001/-/-/-/0//-\n" " 4567 0xb002/0xb003/P/-/-/4/RET/- 0x0/0xb001/-/-/-/0//-\n" " 4567 0xc456/0xc789/P/-/-/13/-/- 0x0/0xc123/-/-/-/0//-\n" " 4567 0xd456/0xd789/M/-/-/7/RET/- 0x0/0xd123/-/-/-/0//-\n" " 4567 0xe005/0xe009/P/-/-/14/RET/- 0x0/0xe001/-/-/-/0//-\n" " 4567 0xd456/0xd789/M/-/-/7/RET/- 0x0/0xd123/-/-/-/0//-\n" " 4567 0xf002/0xf003/MN/-/-/8/COND/- 0x0/0xf001/-/-/-/0//-\n" " 4567 0xc456/0xc789/P/-/-/13/-/- 0x0/0xc123/-/-/-/0//-\n"; // ExpectedSamples contains the aggregated information about // a branch {{From, To, TraceTo}, {TakenCount, MispredCount}}. // Where // - From: is the source address of the sampled branch operation. // - To: is the target address of the sampled branch operation. // - TraceTo could be either // - A 'Type = Trace::BR_ONLY', which means the trace only contains branch // data. // - Or an address, when the trace contains information about the previous // branch. // // When FEAT_SPE_PBT is present, Arm SPE emits two records per sample: // - the current branch (Spe.From/Spe.To), and // - the previous taken branch target (PBT) (PBT.From, PBT.To). // // Together they behave like a depth-1 branch stack where: // - the PBT entry is always taken // - the current branch entry may represent a taken branch or a fall-through // - the destination (Spe.To) is the architecturally executed target // // There can be fall-throughs to be inferred between the PBT entry and // the current branch (Spe.From), but there cannot be between current // branch's (Spe.From/Spe.To). // // PBT records only the target address (PBT.To), meaning we have no // information as the branch source (PBT.From=0x0), branch type, and the // prediction bit. // // Consider the trace pair: // {{Spe.From, Spe.To, Type}, {TK, MP}}, // {{PBT.From, PBT.To, TraceTo}, {TK, MP}} // {{0xd456, 0xd789, Trace::BR_ONLY}, {2, 2}}, {{0x0, 0xd123, 0xd456}, {2, 0}} // // The first entry is the Spe record, which represents a trace from 0xd456 // (Spe.From) to 0xd789 (Spe.To). Type = Trace::BR_ONLY, as Bolt processes the // current branch event first. At this point we have no information about the // previous trace (PBT). This entry has a TakenCount = 2, as we have two // samples for (0xd456, 0xd789) in our input. It also has MispredsCount = 2, // as 'M' misprediction flag appears in both cases. // // The second entry is the PBT record. TakenCount = 2 because the // (PBT.From = 0x0, PBT.To = 0xd123) branch target appears twice in the input, // and MispredsCount = 0 because prediction data is absent. There is no branch // source information, so the PBT.From field is zero (0x0). TraceTo = 0xd456 // connect the flow from the previous taken branch at 0xd123 (PBT.To) to the // current source branch at 0xd456 (Spe.From), which then continues to 0xd789 // (Spe.To). std::vector> ExpectedSamples = { {{0xa002, 0xa003, Trace::BR_ONLY}, {1, 0}}, {{0x0, 0xa001, 0xa002}, {1, 0}}, {{0xb002, 0xb003, Trace::BR_ONLY}, {1, 0}}, {{0x0, 0xb001, 0xb002}, {1, 0}}, {{0xc456, 0xc789, Trace::BR_ONLY}, {2, 0}}, {{0x0, 0xc123, 0xc456}, {2, 0}}, {{0xd456, 0xd789, Trace::BR_ONLY}, {2, 2}}, {{0x0, 0xd123, 0xd456}, {2, 0}}, {{0xe005, 0xe009, Trace::BR_ONLY}, {1, 0}}, {{0x0, 0xe001, 0xe005}, {1, 0}}, {{0xf002, 0xf003, Trace::BR_ONLY}, {1, 1}}, {{0x0, 0xf001, 0xf002}, {1, 0}}}; parseAndCheckBrstackEvents(4567, ExpectedSamples); } #endif