Collect profiles for functions we encounter when collecting a contextual profile, that are not associated with a call site. This is expected to happen for signal handlers, but it also - problematically - currently happens for mem{memset|copy|move|set}, which are currently inserted after profile instrumentation.
Collecting a "regular" flat profile in these cases would hide the problem - that we loose better profile opportunities.
304 lines
11 KiB
C++
304 lines
11 KiB
C++
#include "../CtxInstrProfiling.h"
|
|
#include "gtest/gtest.h"
|
|
#include <thread>
|
|
|
|
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<char>(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<uintptr_t>(FData.FlatCtx) + 1,
|
|
reinterpret_cast<uintptr_t>(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<int> NonScratch = 0;
|
|
std::atomic<int> 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);
|
|
}
|