#include "../CtxInstrProfiling.h" #include "gtest/gtest.h" #include using namespace __ctx_profile; class ContextTest : public ::testing::Test { void SetUp() override { Root.getOrAllocateContextRoot(); } void TearDown() override { __llvm_ctx_profile_free(); } public: FunctionData Root; }; TEST(ArenaTest, ZeroInit) { char Buffer[1024]; memset(Buffer, 1, 1024); Arena *A = new (Buffer) Arena(10); for (auto I = 0U; I < A->size(); ++I) EXPECT_EQ(A->pos()[I], static_cast(0)); EXPECT_EQ(A->size(), 10U); } TEST(ArenaTest, Basic) { Arena *A = Arena::allocateNewArena(1024); EXPECT_EQ(A->size(), 1024U); EXPECT_EQ(A->next(), nullptr); auto *M1 = A->tryBumpAllocate(1020); EXPECT_NE(M1, nullptr); auto *M2 = A->tryBumpAllocate(4); EXPECT_NE(M2, nullptr); EXPECT_EQ(M1 + 1020, M2); EXPECT_EQ(A->tryBumpAllocate(1), nullptr); Arena *A2 = Arena::allocateNewArena(2024, A); EXPECT_EQ(A->next(), A2); EXPECT_EQ(A2->next(), nullptr); Arena::freeArenaList(A); EXPECT_EQ(A, nullptr); } TEST_F(ContextTest, Basic) { __llvm_ctx_profile_start_collection(); auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); ASSERT_NE(Ctx, nullptr); auto &CtxRoot = *Root.CtxRoot; EXPECT_NE(CtxRoot.CurrentMem, nullptr); EXPECT_EQ(CtxRoot.FirstMemBlock, CtxRoot.CurrentMem); EXPECT_EQ(Ctx->size(), sizeof(ContextNode) + 10 * sizeof(uint64_t) + 4 * sizeof(ContextNode *)); EXPECT_EQ(Ctx->counters_size(), 10U); EXPECT_EQ(Ctx->callsites_size(), 4U); EXPECT_EQ(__llvm_ctx_profile_current_context_root, &CtxRoot); CtxRoot.Taken.CheckLocked(); EXPECT_FALSE(CtxRoot.Taken.TryLock()); __llvm_ctx_profile_release_context(&Root); EXPECT_EQ(__llvm_ctx_profile_current_context_root, nullptr); EXPECT_TRUE(CtxRoot.Taken.TryLock()); CtxRoot.Taken.Unlock(); } TEST_F(ContextTest, Callsite) { __llvm_ctx_profile_start_collection(); auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); int FakeCalleeAddress = 0; const bool IsScratch = isScratch(Ctx); EXPECT_FALSE(IsScratch); // This is the sequence the caller performs - it's the lowering of the // instrumentation of the callsite "2". "2" is arbitrary here. __llvm_ctx_profile_expected_callee[0] = &FakeCalleeAddress; __llvm_ctx_profile_callsite[0] = &Ctx->subContexts()[2]; // This is what the callee does FunctionData FData; auto *Subctx = __llvm_ctx_profile_get_context(&FData, &FakeCalleeAddress, 2, 3, 1); // This should not have required creating a flat context. EXPECT_EQ(FData.FlatCtx, nullptr); // We expect the subcontext to be appropriately placed and dimensioned EXPECT_EQ(Ctx->subContexts()[2], Subctx); EXPECT_EQ(Subctx->counters_size(), 3U); EXPECT_EQ(Subctx->callsites_size(), 1U); // We reset these in _get_context. EXPECT_EQ(__llvm_ctx_profile_expected_callee[0], nullptr); EXPECT_EQ(__llvm_ctx_profile_callsite[0], nullptr); EXPECT_EQ(Subctx->size(), sizeof(ContextNode) + 3 * sizeof(uint64_t) + 1 * sizeof(ContextNode *)); __llvm_ctx_profile_release_context(&Root); } TEST_F(ContextTest, ScratchNoCollectionProfilingNotStarted) { // This test intentionally does not call __llvm_ctx_profile_start_collection. EXPECT_EQ(__llvm_ctx_profile_current_context_root, nullptr); int FakeCalleeAddress = 0; // this would be the very first function executing this. the TLS is empty, // too. FunctionData FData; auto *Ctx = __llvm_ctx_profile_get_context(&FData, &FakeCalleeAddress, 2, 3, 1); // We never entered a context (_start_context was never called) - so the // returned context must be a tagged pointer. EXPECT_TRUE(isScratch(Ctx)); // Because we didn't start collection, no flat profile should have been // allocated. EXPECT_EQ(FData.FlatCtx, nullptr); } TEST_F(ContextTest, ScratchNoCollectionProfilingStarted) { ASSERT_EQ(__llvm_ctx_profile_current_context_root, nullptr); int FakeCalleeAddress = 0; // Start collection, so the function gets a flat profile instead of scratch. __llvm_ctx_profile_start_collection(); // this would be the very first function executing this. the TLS is empty, // too. FunctionData FData; auto *Ctx = __llvm_ctx_profile_get_context(&FData, &FakeCalleeAddress, 2, 3, 1); // We never entered a context (_start_context was never called) - so the // returned context must be a tagged pointer. EXPECT_TRUE(isScratch(Ctx)); // Because we never entered a context, we should have allocated a flat context EXPECT_NE(FData.FlatCtx, nullptr); EXPECT_EQ(reinterpret_cast(FData.FlatCtx) + 1, reinterpret_cast(Ctx)); } TEST_F(ContextTest, ScratchDuringCollection) { __llvm_ctx_profile_start_collection(); auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); int FakeCalleeAddress = 0; int OtherFakeCalleeAddress = 0; __llvm_ctx_profile_expected_callee[0] = &FakeCalleeAddress; __llvm_ctx_profile_callsite[0] = &Ctx->subContexts()[2]; FunctionData FData[3]; auto *Subctx = __llvm_ctx_profile_get_context( &FData[0], &OtherFakeCalleeAddress, 2, 3, 1); // We expected a different callee - so return scratch. It mimics what happens // in the case of a signal handler - in this case, OtherFakeCalleeAddress is // the signal handler. EXPECT_TRUE(isScratch(Subctx)); // We shouldn't have tried to return a flat context because we're under a // root. EXPECT_EQ(FData[0].FlatCtx, nullptr); EXPECT_EQ(__llvm_ctx_profile_expected_callee[0], nullptr); EXPECT_EQ(__llvm_ctx_profile_callsite[0], nullptr); int ThirdFakeCalleeAddress = 0; __llvm_ctx_profile_expected_callee[1] = &ThirdFakeCalleeAddress; __llvm_ctx_profile_callsite[1] = &Subctx->subContexts()[0]; auto *Subctx2 = __llvm_ctx_profile_get_context( &FData[1], &ThirdFakeCalleeAddress, 3, 0, 0); // We again expect scratch because the '0' position is where the runtime // looks, so it doesn't matter the '1' position is populated correctly. EXPECT_TRUE(isScratch(Subctx2)); EXPECT_EQ(FData[1].FlatCtx, nullptr); __llvm_ctx_profile_expected_callee[0] = &ThirdFakeCalleeAddress; __llvm_ctx_profile_callsite[0] = &Subctx->subContexts()[0]; auto *Subctx3 = __llvm_ctx_profile_get_context( &FData[2], &ThirdFakeCalleeAddress, 3, 0, 0); // We expect scratch here, too, because the value placed in // __llvm_ctx_profile_callsite is scratch EXPECT_TRUE(isScratch(Subctx3)); EXPECT_EQ(FData[2].FlatCtx, nullptr); __llvm_ctx_profile_release_context(&Root); } TEST_F(ContextTest, NeedMoreMemory) { __llvm_ctx_profile_start_collection(); auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); int FakeCalleeAddress = 0; const bool IsScratch = isScratch(Ctx); EXPECT_FALSE(IsScratch); auto &CtxRoot = *Root.CtxRoot; const auto *CurrentMem = CtxRoot.CurrentMem; __llvm_ctx_profile_expected_callee[0] = &FakeCalleeAddress; __llvm_ctx_profile_callsite[0] = &Ctx->subContexts()[2]; FunctionData FData; // Allocate a massive subcontext to force new arena allocation auto *Subctx = __llvm_ctx_profile_get_context(&FData, &FakeCalleeAddress, 3, 1 << 20, 1); EXPECT_EQ(FData.FlatCtx, nullptr); EXPECT_EQ(Ctx->subContexts()[2], Subctx); EXPECT_NE(CurrentMem, CtxRoot.CurrentMem); EXPECT_NE(CtxRoot.CurrentMem, nullptr); } TEST_F(ContextTest, ConcurrentRootCollection) { std::atomic NonScratch = 0; std::atomic Executions = 0; __sanitizer::Semaphore GotCtx; auto Entrypoint = [&]() { ++Executions; auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); GotCtx.Post(); const bool IS = isScratch(Ctx); NonScratch += (!IS); if (!IS) { GotCtx.Wait(); GotCtx.Wait(); } __llvm_ctx_profile_release_context(&Root); }; std::thread T1(Entrypoint); std::thread T2(Entrypoint); T1.join(); T2.join(); EXPECT_EQ(NonScratch, 1); EXPECT_EQ(Executions, 2); } TEST_F(ContextTest, Dump) { auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); int FakeCalleeAddress = 0; __llvm_ctx_profile_expected_callee[0] = &FakeCalleeAddress; __llvm_ctx_profile_callsite[0] = &Ctx->subContexts()[2]; FunctionData FData; auto *Subctx = __llvm_ctx_profile_get_context(&FData, &FakeCalleeAddress, 2, 3, 1); (void)Subctx; __llvm_ctx_profile_release_context(&Root); class TestProfileWriter : public ProfileWriter { public: ContextRoot *const Root; const size_t Entries; int EnteredSectionCount = 0; int ExitedSectionCount = 0; int EnteredFlatCount = 0; int ExitedFlatCount = 0; int FlatsWritten = 0; bool State = false; TestProfileWriter(ContextRoot *Root, size_t Entries) : Root(Root), Entries(Entries) {} void writeContextual(const ContextNode &Node, const ContextNode *Unhandled, uint64_t TotalRootEntryCount) override { EXPECT_EQ(TotalRootEntryCount, Entries); EXPECT_EQ(EnteredSectionCount, 1); EXPECT_EQ(ExitedSectionCount, 0); EXPECT_FALSE(Root->Taken.TryLock()); EXPECT_EQ(Node.guid(), 1U); EXPECT_EQ(Node.counters()[0], Entries); EXPECT_EQ(Node.counters_size(), 10U); EXPECT_EQ(Node.callsites_size(), 4U); EXPECT_EQ(Node.subContexts()[0], nullptr); EXPECT_EQ(Node.subContexts()[1], nullptr); EXPECT_NE(Node.subContexts()[2], nullptr); EXPECT_EQ(Node.subContexts()[3], nullptr); const auto &SN = *Node.subContexts()[2]; EXPECT_EQ(SN.guid(), 2U); EXPECT_EQ(SN.counters()[0], Entries); EXPECT_EQ(SN.counters_size(), 3U); EXPECT_EQ(SN.callsites_size(), 1U); EXPECT_EQ(SN.subContexts()[0], nullptr); State = true; } void startContextSection() override { ++EnteredSectionCount; } void endContextSection() override { EXPECT_EQ(EnteredSectionCount, 1); ++ExitedSectionCount; } void startFlatSection() override { ++EnteredFlatCount; } void writeFlat(GUID Guid, const uint64_t *Buffer, size_t BufferSize) override { ++FlatsWritten; EXPECT_EQ(BufferSize, 3U); EXPECT_EQ(Buffer[0], 15U); EXPECT_EQ(Buffer[1], 0U); EXPECT_EQ(Buffer[2], 0U); } void endFlatSection() override { ++ExitedFlatCount; } }; TestProfileWriter W(Root.CtxRoot, 1); EXPECT_FALSE(W.State); __llvm_ctx_profile_fetch(W); EXPECT_TRUE(W.State); // this resets all counters but not the internal structure. __llvm_ctx_profile_start_collection(); auto *Flat = __llvm_ctx_profile_get_context(&FData, &FakeCalleeAddress, 2, 3, 1); (void)Flat; EXPECT_NE(FData.FlatCtx, nullptr); FData.FlatCtx->counters()[0] = 15U; TestProfileWriter W2(Root.CtxRoot, 0); EXPECT_FALSE(W2.State); __llvm_ctx_profile_fetch(W2); EXPECT_TRUE(W2.State); EXPECT_EQ(W2.EnteredSectionCount, 1); EXPECT_EQ(W2.ExitedSectionCount, 1); EXPECT_EQ(W2.EnteredFlatCount, 1); EXPECT_EQ(W2.FlatsWritten, 1); EXPECT_EQ(W2.ExitedFlatCount, 1); } TEST_F(ContextTest, MustNotBeRoot) { FunctionData FData; FData.CtxRoot = reinterpret_cast(1U); int FakeCalleeAddress = 0; __llvm_ctx_profile_start_collection(); auto *Subctx = __llvm_ctx_profile_get_context(&FData, &FakeCalleeAddress, 2, 3, 1); EXPECT_TRUE(isScratch(Subctx)); }