aboutsummaryrefslogtreecommitdiff
path: root/bolt/unittests
diff options
context:
space:
mode:
Diffstat (limited to 'bolt/unittests')
-rw-r--r--bolt/unittests/CMakeLists.txt1
-rw-r--r--bolt/unittests/Core/MCPlusBuilder.cpp55
-rw-r--r--bolt/unittests/Passes/CMakeLists.txt30
-rw-r--r--bolt/unittests/Passes/PointerAuthCFIFixup.cpp339
-rw-r--r--bolt/unittests/Profile/PerfSpeEvents.cpp88
5 files changed, 513 insertions, 0 deletions
diff --git a/bolt/unittests/CMakeLists.txt b/bolt/unittests/CMakeLists.txt
index 64414b8..d47ddc4 100644
--- a/bolt/unittests/CMakeLists.txt
+++ b/bolt/unittests/CMakeLists.txt
@@ -7,3 +7,4 @@ endfunction()
add_subdirectory(Core)
add_subdirectory(Profile)
+add_subdirectory(Passes)
diff --git a/bolt/unittests/Core/MCPlusBuilder.cpp b/bolt/unittests/Core/MCPlusBuilder.cpp
index bc37ced..7b6f162 100644
--- a/bolt/unittests/Core/MCPlusBuilder.cpp
+++ b/bolt/unittests/Core/MCPlusBuilder.cpp
@@ -143,6 +143,61 @@ TEST_P(MCPlusBuilderTester, AArch64_CmpJE) {
ASSERT_EQ(Label, BB->getLabel());
}
+TEST_P(MCPlusBuilderTester, AArch64_BTI) {
+ if (GetParam() != Triple::aarch64)
+ GTEST_SKIP();
+ BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
+ std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
+
+ MCInst BTIjc;
+ BC->MIB->createBTI(BTIjc, true, true);
+ BB->addInstruction(BTIjc);
+ auto II = BB->begin();
+ ASSERT_EQ(II->getOpcode(), AArch64::HINT);
+ ASSERT_EQ(II->getOperand(0).getImm(), 38);
+ ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true));
+ BC->MIB->updateBTIVariant(*II, true, false);
+ ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
+
+ MCInst BTIj;
+ BC->MIB->createBTI(BTIj, false, true);
+ II = BB->addInstruction(BTIj);
+ ASSERT_EQ(II->getOpcode(), AArch64::HINT);
+ ASSERT_EQ(II->getOperand(0).getImm(), 36);
+ ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, false, true));
+ BC->MIB->updateBTIVariant(*II, true, true);
+ ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true));
+
+ MCInst BTIc;
+ BC->MIB->createBTI(BTIc, true, false);
+ II = BB->addInstruction(BTIc);
+ ASSERT_EQ(II->getOpcode(), AArch64::HINT);
+ ASSERT_EQ(II->getOperand(0).getImm(), 34);
+ ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
+ BC->MIB->updateBTIVariant(*II, false, true);
+ ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, false, true));
+
+#ifndef NDEBUG
+ MCInst BTIinvalid;
+ ASSERT_DEATH(BC->MIB->createBTI(BTIinvalid, false, false),
+ "No target kinds!");
+#endif
+
+ MCInst Paciasp = MCInstBuilder(AArch64::PACIASP);
+ II = BB->addInstruction(Paciasp);
+ ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
+ ASSERT_FALSE(BC->MIB->isBTILandingPad(*II, true, true));
+ ASSERT_FALSE(BC->MIB->isBTILandingPad(*II, false, true));
+ ASSERT_TRUE(BC->MIB->isImplicitBTIC(*II));
+
+ MCInst Pacibsp = MCInstBuilder(AArch64::PACIBSP);
+ II = BB->addInstruction(Pacibsp);
+ ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
+ ASSERT_FALSE(BC->MIB->isBTILandingPad(*II, true, true));
+ ASSERT_FALSE(BC->MIB->isBTILandingPad(*II, false, true));
+ ASSERT_TRUE(BC->MIB->isImplicitBTIC(*II));
+}
+
TEST_P(MCPlusBuilderTester, AArch64_CmpJNE) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
diff --git a/bolt/unittests/Passes/CMakeLists.txt b/bolt/unittests/Passes/CMakeLists.txt
new file mode 100644
index 0000000..17ae880
--- /dev/null
+++ b/bolt/unittests/Passes/CMakeLists.txt
@@ -0,0 +1,30 @@
+set(LLVM_LINK_COMPONENTS
+ DebugInfoDWARF
+ Object
+ MC
+ ${BOLT_TARGETS_TO_BUILD}
+ )
+
+add_bolt_unittest(PassTests
+ PointerAuthCFIFixup.cpp
+
+ DISABLE_LLVM_LINK_LLVM_DYLIB
+ )
+
+target_link_libraries(PassTests
+ PRIVATE
+ LLVMBOLTCore
+ LLVMBOLTRewrite
+ LLVMBOLTPasses
+ LLVMBOLTProfile
+ LLVMBOLTUtils
+ )
+
+foreach (tgt ${BOLT_TARGETS_TO_BUILD})
+ include_directories(
+ ${LLVM_MAIN_SRC_DIR}/lib/Target/${tgt}
+ ${LLVM_BINARY_DIR}/lib/Target/${tgt}
+ )
+ string(TOUPPER "${tgt}" upper)
+ target_compile_definitions(PassTests PRIVATE "${upper}_AVAILABLE")
+endforeach()
diff --git a/bolt/unittests/Passes/PointerAuthCFIFixup.cpp b/bolt/unittests/Passes/PointerAuthCFIFixup.cpp
new file mode 100644
index 0000000..0d54b31
--- /dev/null
+++ b/bolt/unittests/Passes/PointerAuthCFIFixup.cpp
@@ -0,0 +1,339 @@
+//===- bolt/unittest/Passes/PointerAuthCFIFixup.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 "AArch64Subtarget.h"
+#include "MCTargetDesc/AArch64MCTargetDesc.h"
+#endif // AARCH64_AVAILABLE
+
+#include "bolt/Core/BinaryBasicBlock.h"
+#include "bolt/Core/BinaryFunction.h"
+#include "bolt/Passes/PointerAuthCFIFixup.h"
+#include "bolt/Rewrite/BinaryPassManager.h"
+#include "bolt/Rewrite/RewriteInstance.h"
+#include "bolt/Utils/CommandLineOpts.h"
+#include "llvm/BinaryFormat/ELF.h"
+#include "llvm/MC/MCDwarf.h"
+#include "llvm/MC/MCInstBuilder.h"
+#include "llvm/Support/TargetSelect.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::object;
+using namespace llvm::ELF;
+using namespace bolt;
+
+namespace opts {
+extern cl::opt<bool> PrintPAuthCFIAnalyzer;
+} // namespace opts
+
+namespace {
+struct PassTester : public testing::TestWithParam<Triple::ArchType> {
+ void SetUp() override {
+ initalizeLLVM();
+ prepareElf();
+ initializeBolt();
+ }
+
+protected:
+ void initalizeLLVM() {
+#define BOLT_TARGET(target) \
+ LLVMInitialize##target##TargetInfo(); \
+ LLVMInitialize##target##TargetMC(); \
+ LLVMInitialize##target##AsmParser(); \
+ LLVMInitialize##target##Disassembler(); \
+ LLVMInitialize##target##Target(); \
+ LLVMInitialize##target##AsmPrinter();
+
+#include "bolt/Core/TargetConfig.def"
+ }
+
+#define PREPARE_FUNC(name) \
+ constexpr uint64_t FunctionAddress = 0x1000; \
+ BinaryFunction *BF = BC->createBinaryFunction( \
+ name, *TextSection, FunctionAddress, /*Size=*/0, /*SymbolSize=*/0, \
+ /*Alignment=*/16); \
+ /* Make sure the pass runs on the BF.*/ \
+ BF->updateState(BinaryFunction::State::CFG); \
+ BF->setContainedNegateRAState(); \
+ /* All tests need at least one BB. */ \
+ BinaryBasicBlock *BB = BF->addBasicBlock(); \
+ BF->addEntryPoint(*BB); \
+ BB->setCFIState(0);
+
+ void prepareElf() {
+ memcpy(ElfBuf, "\177ELF", 4);
+ ELF64LE::Ehdr *EHdr = reinterpret_cast<typename ELF64LE::Ehdr *>(ElfBuf);
+ EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64;
+ EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB;
+ EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64;
+ 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<orc::SymbolStringPool>(),
+ ObjFile->getFileName(), nullptr, true, DWARFContext::create(*ObjFile),
+ {llvm::outs(), llvm::errs()}));
+ ASSERT_FALSE(!BC);
+ BC->initializeTarget(std::unique_ptr<MCPlusBuilder>(
+ createMCPlusBuilder(GetParam(), BC->MIA.get(), BC->MII.get(),
+ BC->MRI.get(), BC->STI.get())));
+
+ PassManager = std::make_unique<BinaryFunctionPassManager>(*BC);
+ PassManager->registerPass(
+ std::make_unique<PointerAuthCFIFixup>(opts::PrintPAuthCFIAnalyzer));
+
+ TextSection = &BC->registerOrUpdateSection(
+ ".text", ELF::SHT_PROGBITS, ELF::SHF_ALLOC | ELF::SHF_EXECINSTR,
+ /*Data=*/nullptr, /*Size=*/0,
+ /*Alignment=*/16);
+ }
+
+ std::vector<int> findCFIOffsets(BinaryFunction &BF) {
+ std::vector<int> Locations;
+ int Idx = 0;
+ int InstSize = 4; // AArch64
+ for (BinaryBasicBlock &BB : BF) {
+ for (MCInst &Inst : BB) {
+ if (BC->MIB->isCFI(Inst)) {
+ const MCCFIInstruction *CFI = BF.getCFIFor(Inst);
+ if (CFI->getOperation() == MCCFIInstruction::OpNegateRAState)
+ Locations.push_back(Idx * InstSize);
+ }
+ Idx++;
+ }
+ }
+ return Locations;
+ }
+
+ char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {};
+ std::unique_ptr<ObjectFile> ObjFile;
+ std::unique_ptr<BinaryContext> BC;
+ std::unique_ptr<BinaryFunctionPassManager> PassManager;
+ BinarySection *TextSection;
+};
+} // namespace
+
+TEST_P(PassTester, ExampleTest) {
+ if (GetParam() != Triple::aarch64)
+ GTEST_SKIP();
+
+ ASSERT_NE(TextSection, nullptr);
+
+ PREPARE_FUNC("ExampleFunction");
+
+ MCInst UnsignedInst = MCInstBuilder(AArch64::ADDSXri)
+ .addReg(AArch64::X0)
+ .addReg(AArch64::X0)
+ .addImm(0)
+ .addImm(0);
+ BC->MIB->setRAState(UnsignedInst, false);
+ BB->addInstruction(UnsignedInst);
+
+ MCInst SignedInst = MCInstBuilder(AArch64::ADDSXri)
+ .addReg(AArch64::X0)
+ .addReg(AArch64::X0)
+ .addImm(1)
+ .addImm(0);
+ BC->MIB->setRAState(SignedInst, true);
+ BB->addInstruction(SignedInst);
+
+ Error E = PassManager->runPasses();
+ EXPECT_FALSE(E);
+
+ /* Expected layout of BF after the pass:
+
+ .LBB0 (3 instructions, align : 1)
+ Entry Point
+ CFI State : 0
+ 00000000: adds x0, x0, #0x0
+ 00000004: !CFI $0 ; OpNegateRAState
+ 00000004: adds x0, x0, #0x1
+ CFI State: 0
+ */
+ auto CFILoc = findCFIOffsets(*BF);
+ EXPECT_EQ(CFILoc.size(), 1u);
+ EXPECT_EQ(CFILoc[0], 4);
+}
+
+TEST_P(PassTester, fillUnknownStateInBBTest) {
+ /* Check that a if BB starts with unknown RAState, we can fill the unknown
+ states based on following instructions with known RAStates.
+ *
+ * .LBB0 (1 instructions, align : 1)
+ Entry Point
+ CFI State : 0
+ 00000000: adds x0, x0, #0x0
+ CFI State: 0
+
+ .LBB1 (4 instructions, align : 1)
+ CFI State : 0
+ 00000004: !CFI $0 ; OpNegateRAState
+ 00000004: adds x0, x0, #0x1
+ 00000008: adds x0, x0, #0x2
+ 0000000c: adds x0, x0, #0x3
+ CFI State: 0
+ */
+ if (GetParam() != Triple::aarch64)
+ GTEST_SKIP();
+
+ ASSERT_NE(TextSection, nullptr);
+
+ PREPARE_FUNC("FuncWithUnknownStateInBB");
+ BinaryBasicBlock *BB2 = BF->addBasicBlock();
+ BB2->setCFIState(0);
+
+ MCInst Unsigned = MCInstBuilder(AArch64::ADDSXri)
+ .addReg(AArch64::X0)
+ .addReg(AArch64::X0)
+ .addImm(0)
+ .addImm(0);
+ BC->MIB->setRAState(Unsigned, false);
+ BB->addInstruction(Unsigned);
+
+ MCInst Unknown = MCInstBuilder(AArch64::ADDSXri)
+ .addReg(AArch64::X0)
+ .addReg(AArch64::X0)
+ .addImm(1)
+ .addImm(0);
+ MCInst Unknown1 = MCInstBuilder(AArch64::ADDSXri)
+ .addReg(AArch64::X0)
+ .addReg(AArch64::X0)
+ .addImm(2)
+ .addImm(0);
+ MCInst Signed = MCInstBuilder(AArch64::ADDSXri)
+ .addReg(AArch64::X0)
+ .addReg(AArch64::X0)
+ .addImm(3)
+ .addImm(0);
+ BC->MIB->setRAState(Signed, true);
+ BB2->addInstruction(Unknown);
+ BB2->addInstruction(Unknown1);
+ BB2->addInstruction(Signed);
+
+ Error E = PassManager->runPasses();
+ EXPECT_FALSE(E);
+
+ auto CFILoc = findCFIOffsets(*BF);
+ EXPECT_EQ(CFILoc.size(), 1u);
+ EXPECT_EQ(CFILoc[0], 4);
+ // Check that the pass set Unknown and Unknown1 to signed.
+ // begin() is the CFI, begin() + 1 is Unknown, begin() + 2 is Unknown1.
+ std::optional<bool> RAState = BC->MIB->getRAState(*(BB2->begin() + 1));
+ EXPECT_TRUE(RAState.has_value());
+ EXPECT_TRUE(*RAState);
+ std::optional<bool> RAState1 = BC->MIB->getRAState(*(BB2->begin() + 2));
+ EXPECT_TRUE(RAState1.has_value());
+ EXPECT_TRUE(*RAState1);
+}
+
+TEST_P(PassTester, fillUnknownStubs) {
+ /*
+ * Stubs that are not part of the function's CFG should inherit the RAState of
+ the BasicBlock before it.
+ *
+ * LBB1 is not part of the CFG: LBB0 jumps unconditionally to LBB2.
+ * LBB1 would be a stub inserted in LongJmp in real code.
+ * We do not add any NegateRAState CFIs, as other CFIs are not added either.
+ * See issue #160989 for more details.
+ *
+ * .LBB0 (1 instructions, align : 1)
+ Entry Point
+ 00000000: b .LBB2
+ Successors: .LBB2
+
+ .LBB1 (1 instructions, align : 1)
+ 00000004: ret
+
+ .LBB2 (1 instructions, align : 1)
+ Predecessors: .LBB0
+ 00000008: ret
+ */
+ if (GetParam() != Triple::aarch64)
+ GTEST_SKIP();
+
+ ASSERT_NE(TextSection, nullptr);
+
+ PREPARE_FUNC("FuncWithStub");
+ BinaryBasicBlock *BB2 = BF->addBasicBlock();
+ BB2->setCFIState(0);
+ BinaryBasicBlock *BB3 = BF->addBasicBlock();
+ BB3->setCFIState(0);
+
+ BB->addSuccessor(BB3);
+
+ // Jumping over BB2, to BB3.
+ MCInst Jump;
+ BC->MIB->createUncondBranch(Jump, BB3->getLabel(), BC->Ctx.get());
+ BB->addInstruction(Jump);
+ BC->MIB->setRAState(Jump, false);
+
+ // BB2, in real code it would be a ShortJmp.
+ // Unknown RAState.
+ MCInst StubInst;
+ BC->MIB->createReturn(StubInst);
+ BB2->addInstruction(StubInst);
+
+ // Can be any instruction.
+ MCInst Ret;
+ BC->MIB->createReturn(Ret);
+ BB3->addInstruction(Ret);
+ BC->MIB->setRAState(Ret, false);
+
+ Error E = PassManager->runPasses();
+ EXPECT_FALSE(E);
+
+ // Check that we did not generate any NegateRAState CFIs.
+ auto CFILoc = findCFIOffsets(*BF);
+ EXPECT_EQ(CFILoc.size(), 0u);
+}
+
+TEST_P(PassTester, fillUnknownStubsEmpty) {
+ /*
+ * This test checks that BOLT can set the RAState of unknown BBs,
+ * even if all previous BBs are empty, hence no PrevInst gets set.
+ *
+ * As this means that the current (empty) BB is the first with non-pseudo
+ * instructions, the function's initialRAState should be used.
+ */
+ if (GetParam() != Triple::aarch64)
+ GTEST_SKIP();
+
+ ASSERT_NE(TextSection, nullptr);
+
+ PREPARE_FUNC("FuncWithStub");
+ BF->setInitialRAState(false);
+ BinaryBasicBlock *BB2 = BF->addBasicBlock();
+ BB2->setCFIState(0);
+
+ // BB is empty.
+ BB->addSuccessor(BB2);
+
+ // BB2, in real code it would be a ShortJmp.
+ // Unknown RAState.
+ MCInst StubInst;
+ BC->MIB->createReturn(StubInst);
+ BB2->addInstruction(StubInst);
+
+ Error E = PassManager->runPasses();
+ EXPECT_FALSE(E);
+
+ // Check that BOLT added an RAState to BB2.
+ std::optional<bool> RAState = BC->MIB->getRAState(*(BB2->begin()));
+ EXPECT_TRUE(RAState.has_value());
+ // BB2 should be set to BF.initialRAState (false).
+ EXPECT_FALSE(*RAState);
+}
+
+#ifdef AARCH64_AVAILABLE
+INSTANTIATE_TEST_SUITE_P(AArch64, PassTester,
+ ::testing::Values(Triple::aarch64));
+#endif
diff --git a/bolt/unittests/Profile/PerfSpeEvents.cpp b/bolt/unittests/Profile/PerfSpeEvents.cpp
index 8d023cd..4f060cd 100644
--- a/bolt/unittests/Profile/PerfSpeEvents.cpp
+++ b/bolt/unittests/Profile/PerfSpeEvents.cpp
@@ -161,4 +161,92 @@ TEST_F(PerfSpeEventsTestHelper, SpeBranchesWithBrstack) {
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 =
+ // "<PID> <SRC>/<DEST>/PN/-/-/10/COND/- <NULL>/<PBT>/-/-/-/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<std::pair<Trace, TakenBranchInfo>> 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