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 | |