| 1 | #include "../CtxInstrProfiling.h" |
| 2 | #include "gtest/gtest.h" |
| 3 | #include <thread> |
| 4 | |
| 5 | using namespace __ctx_profile; |
| 6 | |
| 7 | class ContextTest : public ::testing::Test { |
| 8 | void SetUp() override { Root.getOrAllocateContextRoot(); } |
| 9 | void TearDown() override { __llvm_ctx_profile_free(); } |
| 10 | |
| 11 | public: |
| 12 | FunctionData Root; |
| 13 | }; |
| 14 | |
| 15 | TEST(ArenaTest, ZeroInit) { |
| 16 | char Buffer[1024]; |
| 17 | memset(Buffer, 1, 1024); |
| 18 | Arena *A = new (Buffer) Arena(10); |
| 19 | for (auto I = 0U; I < A->size(); ++I) |
| 20 | EXPECT_EQ(A->pos()[I], static_cast<char>(0)); |
| 21 | EXPECT_EQ(A->size(), 10U); |
| 22 | } |
| 23 | |
| 24 | TEST(ArenaTest, Basic) { |
| 25 | Arena *A = Arena::allocateNewArena(Size: 1024); |
| 26 | EXPECT_EQ(A->size(), 1024U); |
| 27 | EXPECT_EQ(A->next(), nullptr); |
| 28 | |
| 29 | auto *M1 = A->tryBumpAllocate(S: 1020); |
| 30 | EXPECT_NE(M1, nullptr); |
| 31 | auto *M2 = A->tryBumpAllocate(S: 4); |
| 32 | EXPECT_NE(M2, nullptr); |
| 33 | EXPECT_EQ(M1 + 1020, M2); |
| 34 | EXPECT_EQ(A->tryBumpAllocate(S: 1), nullptr); |
| 35 | Arena *A2 = Arena::allocateNewArena(Size: 2024, Prev: A); |
| 36 | EXPECT_EQ(A->next(), A2); |
| 37 | EXPECT_EQ(A2->next(), nullptr); |
| 38 | Arena::freeArenaList(A); |
| 39 | EXPECT_EQ(A, nullptr); |
| 40 | } |
| 41 | |
| 42 | TEST_F(ContextTest, Basic) { |
| 43 | __llvm_ctx_profile_start_collection(); |
| 44 | auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); |
| 45 | ASSERT_NE(Ctx, nullptr); |
| 46 | auto &CtxRoot = *Root.CtxRoot; |
| 47 | EXPECT_NE(CtxRoot.CurrentMem, nullptr); |
| 48 | EXPECT_EQ(CtxRoot.FirstMemBlock, CtxRoot.CurrentMem); |
| 49 | EXPECT_EQ(Ctx->size(), sizeof(ContextNode) + 10 * sizeof(uint64_t) + |
| 50 | 4 * sizeof(ContextNode *)); |
| 51 | EXPECT_EQ(Ctx->counters_size(), 10U); |
| 52 | EXPECT_EQ(Ctx->callsites_size(), 4U); |
| 53 | EXPECT_EQ(__llvm_ctx_profile_current_context_root, &CtxRoot); |
| 54 | CtxRoot.Taken.CheckLocked(); |
| 55 | EXPECT_FALSE(CtxRoot.Taken.TryLock()); |
| 56 | __llvm_ctx_profile_release_context(&Root); |
| 57 | EXPECT_EQ(__llvm_ctx_profile_current_context_root, nullptr); |
| 58 | EXPECT_TRUE(CtxRoot.Taken.TryLock()); |
| 59 | CtxRoot.Taken.Unlock(); |
| 60 | } |
| 61 | |
| 62 | TEST_F(ContextTest, Callsite) { |
| 63 | __llvm_ctx_profile_start_collection(); |
| 64 | auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); |
| 65 | int FakeCalleeAddress = 0; |
| 66 | const bool IsScratch = isScratch(Ctx); |
| 67 | EXPECT_FALSE(IsScratch); |
| 68 | // This is the sequence the caller performs - it's the lowering of the |
| 69 | // instrumentation of the callsite "2". "2" is arbitrary here. |
| 70 | __llvm_ctx_profile_expected_callee[0] = &FakeCalleeAddress; |
| 71 | __llvm_ctx_profile_callsite[0] = &Ctx->subContexts()[2]; |
| 72 | // This is what the callee does |
| 73 | FunctionData FData; |
| 74 | auto *Subctx = |
| 75 | __llvm_ctx_profile_get_context(FData: &FData, Callee: &FakeCalleeAddress, Guid: 2, NumCounters: 3, NumCallsites: 1); |
| 76 | // This should not have required creating a flat context. |
| 77 | EXPECT_EQ(FData.FlatCtx, nullptr); |
| 78 | // We expect the subcontext to be appropriately placed and dimensioned |
| 79 | EXPECT_EQ(Ctx->subContexts()[2], Subctx); |
| 80 | EXPECT_EQ(Subctx->counters_size(), 3U); |
| 81 | EXPECT_EQ(Subctx->callsites_size(), 1U); |
| 82 | // We reset these in _get_context. |
| 83 | EXPECT_EQ(__llvm_ctx_profile_expected_callee[0], nullptr); |
| 84 | EXPECT_EQ(__llvm_ctx_profile_callsite[0], nullptr); |
| 85 | |
| 86 | EXPECT_EQ(Subctx->size(), sizeof(ContextNode) + 3 * sizeof(uint64_t) + |
| 87 | 1 * sizeof(ContextNode *)); |
| 88 | __llvm_ctx_profile_release_context(&Root); |
| 89 | } |
| 90 | |
| 91 | TEST_F(ContextTest, ScratchNoCollectionProfilingNotStarted) { |
| 92 | // This test intentionally does not call __llvm_ctx_profile_start_collection. |
| 93 | EXPECT_EQ(__llvm_ctx_profile_current_context_root, nullptr); |
| 94 | int FakeCalleeAddress = 0; |
| 95 | // this would be the very first function executing this. the TLS is empty, |
| 96 | // too. |
| 97 | FunctionData FData; |
| 98 | auto *Ctx = |
| 99 | __llvm_ctx_profile_get_context(FData: &FData, Callee: &FakeCalleeAddress, Guid: 2, NumCounters: 3, NumCallsites: 1); |
| 100 | // We never entered a context (_start_context was never called) - so the |
| 101 | // returned context must be a tagged pointer. |
| 102 | EXPECT_TRUE(isScratch(Ctx)); |
| 103 | // Because we didn't start collection, no flat profile should have been |
| 104 | // allocated. |
| 105 | EXPECT_EQ(FData.FlatCtx, nullptr); |
| 106 | } |
| 107 | |
| 108 | TEST_F(ContextTest, ScratchNoCollectionProfilingStarted) { |
| 109 | ASSERT_EQ(__llvm_ctx_profile_current_context_root, nullptr); |
| 110 | int FakeCalleeAddress = 0; |
| 111 | // Start collection, so the function gets a flat profile instead of scratch. |
| 112 | __llvm_ctx_profile_start_collection(); |
| 113 | // this would be the very first function executing this. the TLS is empty, |
| 114 | // too. |
| 115 | FunctionData FData; |
| 116 | auto *Ctx = |
| 117 | __llvm_ctx_profile_get_context(FData: &FData, Callee: &FakeCalleeAddress, Guid: 2, NumCounters: 3, NumCallsites: 1); |
| 118 | // We never entered a context (_start_context was never called) - so the |
| 119 | // returned context must be a tagged pointer. |
| 120 | EXPECT_TRUE(isScratch(Ctx)); |
| 121 | // Because we never entered a context, we should have allocated a flat context |
| 122 | EXPECT_NE(FData.FlatCtx, nullptr); |
| 123 | EXPECT_EQ(reinterpret_cast<uintptr_t>(FData.FlatCtx) + 1, |
| 124 | reinterpret_cast<uintptr_t>(Ctx)); |
| 125 | } |
| 126 | |
| 127 | TEST_F(ContextTest, ScratchDuringCollection) { |
| 128 | __llvm_ctx_profile_start_collection(); |
| 129 | auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); |
| 130 | int FakeCalleeAddress = 0; |
| 131 | int OtherFakeCalleeAddress = 0; |
| 132 | __llvm_ctx_profile_expected_callee[0] = &FakeCalleeAddress; |
| 133 | __llvm_ctx_profile_callsite[0] = &Ctx->subContexts()[2]; |
| 134 | FunctionData FData[3]; |
| 135 | auto *Subctx = __llvm_ctx_profile_get_context( |
| 136 | FData: &FData[0], Callee: &OtherFakeCalleeAddress, Guid: 2, NumCounters: 3, NumCallsites: 1); |
| 137 | // We expected a different callee - so return scratch. It mimics what happens |
| 138 | // in the case of a signal handler - in this case, OtherFakeCalleeAddress is |
| 139 | // the signal handler. |
| 140 | EXPECT_TRUE(isScratch(Ctx: Subctx)); |
| 141 | // We shouldn't have tried to return a flat context because we're under a |
| 142 | // root. |
| 143 | EXPECT_EQ(FData[0].FlatCtx, nullptr); |
| 144 | EXPECT_EQ(__llvm_ctx_profile_expected_callee[0], nullptr); |
| 145 | EXPECT_EQ(__llvm_ctx_profile_callsite[0], nullptr); |
| 146 | |
| 147 | int ThirdFakeCalleeAddress = 0; |
| 148 | __llvm_ctx_profile_expected_callee[1] = &ThirdFakeCalleeAddress; |
| 149 | __llvm_ctx_profile_callsite[1] = &Subctx->subContexts()[0]; |
| 150 | |
| 151 | auto *Subctx2 = __llvm_ctx_profile_get_context( |
| 152 | FData: &FData[1], Callee: &ThirdFakeCalleeAddress, Guid: 3, NumCounters: 0, NumCallsites: 0); |
| 153 | // We again expect scratch because the '0' position is where the runtime |
| 154 | // looks, so it doesn't matter the '1' position is populated correctly. |
| 155 | EXPECT_TRUE(isScratch(Ctx: Subctx2)); |
| 156 | EXPECT_EQ(FData[1].FlatCtx, nullptr); |
| 157 | |
| 158 | __llvm_ctx_profile_expected_callee[0] = &ThirdFakeCalleeAddress; |
| 159 | __llvm_ctx_profile_callsite[0] = &Subctx->subContexts()[0]; |
| 160 | auto *Subctx3 = __llvm_ctx_profile_get_context( |
| 161 | FData: &FData[2], Callee: &ThirdFakeCalleeAddress, Guid: 3, NumCounters: 0, NumCallsites: 0); |
| 162 | // We expect scratch here, too, because the value placed in |
| 163 | // __llvm_ctx_profile_callsite is scratch |
| 164 | EXPECT_TRUE(isScratch(Ctx: Subctx3)); |
| 165 | EXPECT_EQ(FData[2].FlatCtx, nullptr); |
| 166 | |
| 167 | __llvm_ctx_profile_release_context(&Root); |
| 168 | } |
| 169 | |
| 170 | TEST_F(ContextTest, NeedMoreMemory) { |
| 171 | __llvm_ctx_profile_start_collection(); |
| 172 | auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); |
| 173 | int FakeCalleeAddress = 0; |
| 174 | const bool IsScratch = isScratch(Ctx); |
| 175 | EXPECT_FALSE(IsScratch); |
| 176 | auto &CtxRoot = *Root.CtxRoot; |
| 177 | const auto *CurrentMem = CtxRoot.CurrentMem; |
| 178 | __llvm_ctx_profile_expected_callee[0] = &FakeCalleeAddress; |
| 179 | __llvm_ctx_profile_callsite[0] = &Ctx->subContexts()[2]; |
| 180 | FunctionData FData; |
| 181 | // Allocate a massive subcontext to force new arena allocation |
| 182 | auto *Subctx = |
| 183 | __llvm_ctx_profile_get_context(FData: &FData, Callee: &FakeCalleeAddress, Guid: 3, NumCounters: 1 << 20, NumCallsites: 1); |
| 184 | EXPECT_EQ(FData.FlatCtx, nullptr); |
| 185 | EXPECT_EQ(Ctx->subContexts()[2], Subctx); |
| 186 | EXPECT_NE(CurrentMem, CtxRoot.CurrentMem); |
| 187 | EXPECT_NE(CtxRoot.CurrentMem, nullptr); |
| 188 | } |
| 189 | |
| 190 | TEST_F(ContextTest, ConcurrentRootCollection) { |
| 191 | std::atomic<int> NonScratch = 0; |
| 192 | std::atomic<int> Executions = 0; |
| 193 | |
| 194 | __sanitizer::Semaphore GotCtx; |
| 195 | |
| 196 | auto Entrypoint = [&]() { |
| 197 | ++Executions; |
| 198 | auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); |
| 199 | GotCtx.Post(); |
| 200 | const bool IS = isScratch(Ctx); |
| 201 | NonScratch += (!IS); |
| 202 | if (!IS) { |
| 203 | GotCtx.Wait(); |
| 204 | GotCtx.Wait(); |
| 205 | } |
| 206 | __llvm_ctx_profile_release_context(&Root); |
| 207 | }; |
| 208 | std::thread T1(Entrypoint); |
| 209 | std::thread T2(Entrypoint); |
| 210 | T1.join(); |
| 211 | T2.join(); |
| 212 | EXPECT_EQ(NonScratch, 1); |
| 213 | EXPECT_EQ(Executions, 2); |
| 214 | } |
| 215 | |
| 216 | TEST_F(ContextTest, Dump) { |
| 217 | auto *Ctx = __llvm_ctx_profile_start_context(&Root, 1, 10, 4); |
| 218 | int FakeCalleeAddress = 0; |
| 219 | __llvm_ctx_profile_expected_callee[0] = &FakeCalleeAddress; |
| 220 | __llvm_ctx_profile_callsite[0] = &Ctx->subContexts()[2]; |
| 221 | FunctionData FData; |
| 222 | auto *Subctx = |
| 223 | __llvm_ctx_profile_get_context(FData: &FData, Callee: &FakeCalleeAddress, Guid: 2, NumCounters: 3, NumCallsites: 1); |
| 224 | (void)Subctx; |
| 225 | __llvm_ctx_profile_release_context(&Root); |
| 226 | |
| 227 | class TestProfileWriter : public ProfileWriter { |
| 228 | public: |
| 229 | ContextRoot *const Root; |
| 230 | const size_t Entries; |
| 231 | |
| 232 | int EnteredSectionCount = 0; |
| 233 | int ExitedSectionCount = 0; |
| 234 | int EnteredFlatCount = 0; |
| 235 | int ExitedFlatCount = 0; |
| 236 | int FlatsWritten = 0; |
| 237 | |
| 238 | bool State = false; |
| 239 | |
| 240 | TestProfileWriter(ContextRoot *Root, size_t Entries) |
| 241 | : Root(Root), Entries(Entries) {} |
| 242 | |
| 243 | void writeContextual(const ContextNode &Node, const ContextNode *Unhandled, |
| 244 | uint64_t TotalRootEntryCount) override { |
| 245 | EXPECT_EQ(TotalRootEntryCount, Entries); |
| 246 | EXPECT_EQ(EnteredSectionCount, 1); |
| 247 | EXPECT_EQ(ExitedSectionCount, 0); |
| 248 | EXPECT_FALSE(Root->Taken.TryLock()); |
| 249 | EXPECT_EQ(Node.guid(), 1U); |
| 250 | EXPECT_EQ(Node.counters()[0], Entries); |
| 251 | EXPECT_EQ(Node.counters_size(), 10U); |
| 252 | EXPECT_EQ(Node.callsites_size(), 4U); |
| 253 | EXPECT_EQ(Node.subContexts()[0], nullptr); |
| 254 | EXPECT_EQ(Node.subContexts()[1], nullptr); |
| 255 | EXPECT_NE(Node.subContexts()[2], nullptr); |
| 256 | EXPECT_EQ(Node.subContexts()[3], nullptr); |
| 257 | const auto &SN = *Node.subContexts()[2]; |
| 258 | EXPECT_EQ(SN.guid(), 2U); |
| 259 | EXPECT_EQ(SN.counters()[0], Entries); |
| 260 | EXPECT_EQ(SN.counters_size(), 3U); |
| 261 | EXPECT_EQ(SN.callsites_size(), 1U); |
| 262 | EXPECT_EQ(SN.subContexts()[0], nullptr); |
| 263 | State = true; |
| 264 | } |
| 265 | void startContextSection() override { ++EnteredSectionCount; } |
| 266 | void endContextSection() override { |
| 267 | EXPECT_EQ(EnteredSectionCount, 1); |
| 268 | ++ExitedSectionCount; |
| 269 | } |
| 270 | void startFlatSection() override { ++EnteredFlatCount; } |
| 271 | void writeFlat(GUID Guid, const uint64_t *Buffer, |
| 272 | size_t BufferSize) override { |
| 273 | ++FlatsWritten; |
| 274 | EXPECT_EQ(BufferSize, 3U); |
| 275 | EXPECT_EQ(Buffer[0], 15U); |
| 276 | EXPECT_EQ(Buffer[1], 0U); |
| 277 | EXPECT_EQ(Buffer[2], 0U); |
| 278 | } |
| 279 | void endFlatSection() override { ++ExitedFlatCount; } |
| 280 | }; |
| 281 | |
| 282 | TestProfileWriter W(Root.CtxRoot, 1); |
| 283 | EXPECT_FALSE(W.State); |
| 284 | __llvm_ctx_profile_fetch(W); |
| 285 | EXPECT_TRUE(W.State); |
| 286 | |
| 287 | // this resets all counters but not the internal structure. |
| 288 | __llvm_ctx_profile_start_collection(); |
| 289 | auto *Flat = |
| 290 | __llvm_ctx_profile_get_context(FData: &FData, Callee: &FakeCalleeAddress, Guid: 2, NumCounters: 3, NumCallsites: 1); |
| 291 | (void)Flat; |
| 292 | EXPECT_NE(FData.FlatCtx, nullptr); |
| 293 | FData.FlatCtx->counters()[0] = 15U; |
| 294 | TestProfileWriter W2(Root.CtxRoot, 0); |
| 295 | EXPECT_FALSE(W2.State); |
| 296 | __llvm_ctx_profile_fetch(W2); |
| 297 | EXPECT_TRUE(W2.State); |
| 298 | EXPECT_EQ(W2.EnteredSectionCount, 1); |
| 299 | EXPECT_EQ(W2.ExitedSectionCount, 1); |
| 300 | EXPECT_EQ(W2.EnteredFlatCount, 1); |
| 301 | EXPECT_EQ(W2.FlatsWritten, 1); |
| 302 | EXPECT_EQ(W2.ExitedFlatCount, 1); |
| 303 | } |
| 304 | |
| 305 | TEST_F(ContextTest, MustNotBeRoot) { |
| 306 | FunctionData FData; |
| 307 | FData.CtxRoot = reinterpret_cast<ContextRoot *>(1U); |
| 308 | int FakeCalleeAddress = 0; |
| 309 | __llvm_ctx_profile_start_collection(); |
| 310 | auto *Subctx = |
| 311 | __llvm_ctx_profile_get_context(FData: &FData, Callee: &FakeCalleeAddress, Guid: 2, NumCounters: 3, NumCallsites: 1); |
| 312 | EXPECT_TRUE(isScratch(Ctx: Subctx)); |
| 313 | } |
| 314 | |