1bc0d697dSNico Weber //===-- function_call_trie_test.cpp ---------------------------------------===//
2bc0d697dSNico Weber //
3bc0d697dSNico Weber // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4bc0d697dSNico Weber // See https://llvm.org/LICENSE.txt for license information.
5bc0d697dSNico Weber // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6bc0d697dSNico Weber //
7bc0d697dSNico Weber //===----------------------------------------------------------------------===//
8bc0d697dSNico Weber //
9bc0d697dSNico Weber // This file is a part of XRay, a function call tracing system.
10bc0d697dSNico Weber //
11bc0d697dSNico Weber //===----------------------------------------------------------------------===//
12bc0d697dSNico Weber #include "xray_function_call_trie.h"
13bc0d697dSNico Weber #include "gtest/gtest.h"
14bc0d697dSNico Weber #include <cstdint>
15bc0d697dSNico Weber
16bc0d697dSNico Weber namespace __xray {
17bc0d697dSNico Weber
18bc0d697dSNico Weber namespace {
19bc0d697dSNico Weber
TEST(FunctionCallTrieTest,ConstructWithTLSAllocators)20bc0d697dSNico Weber TEST(FunctionCallTrieTest, ConstructWithTLSAllocators) {
21bc0d697dSNico Weber profilingFlags()->setDefaults();
22bc0d697dSNico Weber FunctionCallTrie::Allocators Allocators = FunctionCallTrie::InitAllocators();
23bc0d697dSNico Weber FunctionCallTrie Trie(Allocators);
24bc0d697dSNico Weber }
25bc0d697dSNico Weber
TEST(FunctionCallTrieTest,EnterAndExitFunction)26bc0d697dSNico Weber TEST(FunctionCallTrieTest, EnterAndExitFunction) {
27bc0d697dSNico Weber profilingFlags()->setDefaults();
28bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
29bc0d697dSNico Weber FunctionCallTrie Trie(A);
30bc0d697dSNico Weber
31bc0d697dSNico Weber uint64_t TSC = 1;
32bc0d697dSNico Weber uint16_t CPU = 0;
33bc0d697dSNico Weber Trie.enterFunction(1, TSC++, CPU++);
34bc0d697dSNico Weber Trie.exitFunction(1, TSC++, CPU++);
35bc0d697dSNico Weber const auto &R = Trie.getRoots();
36bc0d697dSNico Weber
37bc0d697dSNico Weber ASSERT_EQ(R.size(), 1u);
38bc0d697dSNico Weber ASSERT_EQ(R.front()->FId, 1);
39bc0d697dSNico Weber ASSERT_EQ(R.front()->CallCount, 1u);
40bc0d697dSNico Weber ASSERT_EQ(R.front()->CumulativeLocalTime, 1u);
41bc0d697dSNico Weber }
42bc0d697dSNico Weber
TEST(FunctionCallTrieTest,HandleTSCOverflow)43bc0d697dSNico Weber TEST(FunctionCallTrieTest, HandleTSCOverflow) {
44bc0d697dSNico Weber profilingFlags()->setDefaults();
45bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
46bc0d697dSNico Weber FunctionCallTrie Trie(A);
47bc0d697dSNico Weber
48bc0d697dSNico Weber Trie.enterFunction(1, std::numeric_limits<uint64_t>::max(), 0);
49bc0d697dSNico Weber Trie.exitFunction(1, 1, 0);
50bc0d697dSNico Weber const auto &R = Trie.getRoots();
51bc0d697dSNico Weber
52bc0d697dSNico Weber ASSERT_EQ(R.size(), 1u);
53bc0d697dSNico Weber ASSERT_EQ(R.front()->FId, 1);
54bc0d697dSNico Weber ASSERT_EQ(R.front()->CallCount, 1u);
55bc0d697dSNico Weber ASSERT_EQ(R.front()->CumulativeLocalTime, 1u);
56bc0d697dSNico Weber }
57bc0d697dSNico Weber
TEST(FunctionCallTrieTest,MaximalCumulativeTime)58bc0d697dSNico Weber TEST(FunctionCallTrieTest, MaximalCumulativeTime) {
59bc0d697dSNico Weber profilingFlags()->setDefaults();
60bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
61bc0d697dSNico Weber FunctionCallTrie Trie(A);
62bc0d697dSNico Weber
63bc0d697dSNico Weber Trie.enterFunction(1, 1, 0);
64bc0d697dSNico Weber Trie.exitFunction(1, 0, 0);
65bc0d697dSNico Weber const auto &R = Trie.getRoots();
66bc0d697dSNico Weber
67bc0d697dSNico Weber ASSERT_EQ(R.size(), 1u);
68bc0d697dSNico Weber ASSERT_EQ(R.front()->FId, 1);
69bc0d697dSNico Weber ASSERT_EQ(R.front()->CallCount, 1u);
70bc0d697dSNico Weber ASSERT_EQ(R.front()->CumulativeLocalTime,
71bc0d697dSNico Weber std::numeric_limits<uint64_t>::max() - 1);
72bc0d697dSNico Weber }
73bc0d697dSNico Weber
TEST(FunctionCallTrieTest,MissingFunctionEntry)74bc0d697dSNico Weber TEST(FunctionCallTrieTest, MissingFunctionEntry) {
75bc0d697dSNico Weber profilingFlags()->setDefaults();
76bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
77bc0d697dSNico Weber FunctionCallTrie Trie(A);
78bc0d697dSNico Weber Trie.exitFunction(1, 1, 0);
79bc0d697dSNico Weber const auto &R = Trie.getRoots();
80bc0d697dSNico Weber
81bc0d697dSNico Weber ASSERT_TRUE(R.empty());
82bc0d697dSNico Weber }
83bc0d697dSNico Weber
TEST(FunctionCallTrieTest,NoMatchingEntersForExit)84bc0d697dSNico Weber TEST(FunctionCallTrieTest, NoMatchingEntersForExit) {
85bc0d697dSNico Weber profilingFlags()->setDefaults();
86bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
87bc0d697dSNico Weber FunctionCallTrie Trie(A);
88bc0d697dSNico Weber Trie.enterFunction(2, 1, 0);
89bc0d697dSNico Weber Trie.enterFunction(3, 3, 0);
90bc0d697dSNico Weber Trie.exitFunction(1, 5, 0);
91bc0d697dSNico Weber const auto &R = Trie.getRoots();
92bc0d697dSNico Weber
93bc0d697dSNico Weber ASSERT_FALSE(R.empty());
94bc0d697dSNico Weber EXPECT_EQ(R.size(), size_t{1});
95bc0d697dSNico Weber }
96bc0d697dSNico Weber
TEST(FunctionCallTrieTest,MissingFunctionExit)97bc0d697dSNico Weber TEST(FunctionCallTrieTest, MissingFunctionExit) {
98bc0d697dSNico Weber profilingFlags()->setDefaults();
99bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
100bc0d697dSNico Weber FunctionCallTrie Trie(A);
101bc0d697dSNico Weber Trie.enterFunction(1, 1, 0);
102bc0d697dSNico Weber const auto &R = Trie.getRoots();
103bc0d697dSNico Weber
104bc0d697dSNico Weber ASSERT_FALSE(R.empty());
105bc0d697dSNico Weber EXPECT_EQ(R.size(), size_t{1});
106bc0d697dSNico Weber }
107bc0d697dSNico Weber
TEST(FunctionCallTrieTest,MultipleRoots)108bc0d697dSNico Weber TEST(FunctionCallTrieTest, MultipleRoots) {
109bc0d697dSNico Weber profilingFlags()->setDefaults();
110bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
111bc0d697dSNico Weber FunctionCallTrie Trie(A);
112bc0d697dSNico Weber
113bc0d697dSNico Weber // Enter and exit FId = 1.
114bc0d697dSNico Weber Trie.enterFunction(1, 1, 0);
115bc0d697dSNico Weber Trie.exitFunction(1, 2, 0);
116bc0d697dSNico Weber
117bc0d697dSNico Weber // Enter and exit FId = 2.
118bc0d697dSNico Weber Trie.enterFunction(2, 3, 0);
119bc0d697dSNico Weber Trie.exitFunction(2, 4, 0);
120bc0d697dSNico Weber
121bc0d697dSNico Weber const auto &R = Trie.getRoots();
122bc0d697dSNico Weber ASSERT_FALSE(R.empty());
123bc0d697dSNico Weber ASSERT_EQ(R.size(), 2u);
124bc0d697dSNico Weber
125bc0d697dSNico Weber // Make sure the roots have different IDs.
126bc0d697dSNico Weber const auto R0 = R[0];
127bc0d697dSNico Weber const auto R1 = R[1];
128bc0d697dSNico Weber ASSERT_NE(R0->FId, R1->FId);
129bc0d697dSNico Weber
130bc0d697dSNico Weber // Inspect the roots that they have the right data.
131bc0d697dSNico Weber ASSERT_NE(R0, nullptr);
132bc0d697dSNico Weber EXPECT_EQ(R0->CallCount, 1u);
133bc0d697dSNico Weber EXPECT_EQ(R0->CumulativeLocalTime, 1u);
134bc0d697dSNico Weber
135bc0d697dSNico Weber ASSERT_NE(R1, nullptr);
136bc0d697dSNico Weber EXPECT_EQ(R1->CallCount, 1u);
137bc0d697dSNico Weber EXPECT_EQ(R1->CumulativeLocalTime, 1u);
138bc0d697dSNico Weber }
139bc0d697dSNico Weber
140bc0d697dSNico Weber // While missing an intermediary entry may be rare in practice, we still enforce
141bc0d697dSNico Weber // that we can handle the case where we've missed the entry event somehow, in
142bc0d697dSNico Weber // between call entry/exits. To illustrate, imagine the following shadow call
143bc0d697dSNico Weber // stack:
144bc0d697dSNico Weber //
145bc0d697dSNico Weber // f0@t0 -> f1@t1 -> f2@t2
146bc0d697dSNico Weber //
147bc0d697dSNico Weber // If for whatever reason we see an exit for `f2` @ t3, followed by an exit for
148bc0d697dSNico Weber // `f0` @ t4 (i.e. no `f1` exit in between) then we need to handle the case of
149bc0d697dSNico Weber // accounting local time to `f2` from d = (t3 - t2), then local time to `f1`
150bc0d697dSNico Weber // as d' = (t3 - t1) - d, and then local time to `f0` as d'' = (t3 - t0) - d'.
TEST(FunctionCallTrieTest,MissingIntermediaryExit)151bc0d697dSNico Weber TEST(FunctionCallTrieTest, MissingIntermediaryExit) {
152bc0d697dSNico Weber profilingFlags()->setDefaults();
153bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
154bc0d697dSNico Weber FunctionCallTrie Trie(A);
155bc0d697dSNico Weber
156bc0d697dSNico Weber Trie.enterFunction(1, 0, 0);
157bc0d697dSNico Weber Trie.enterFunction(2, 100, 0);
158bc0d697dSNico Weber Trie.enterFunction(3, 200, 0);
159bc0d697dSNico Weber Trie.exitFunction(3, 300, 0);
160bc0d697dSNico Weber Trie.exitFunction(1, 400, 0);
161bc0d697dSNico Weber
162bc0d697dSNico Weber // What we should see at this point is all the functions in the trie in a
163bc0d697dSNico Weber // specific order (1 -> 2 -> 3) with the appropriate count(s) and local
164bc0d697dSNico Weber // latencies.
165bc0d697dSNico Weber const auto &R = Trie.getRoots();
166bc0d697dSNico Weber ASSERT_FALSE(R.empty());
167bc0d697dSNico Weber ASSERT_EQ(R.size(), 1u);
168bc0d697dSNico Weber
169bc0d697dSNico Weber const auto &F1 = *R[0];
170bc0d697dSNico Weber ASSERT_EQ(F1.FId, 1);
171bc0d697dSNico Weber ASSERT_FALSE(F1.Callees.empty());
172bc0d697dSNico Weber
173bc0d697dSNico Weber const auto &F2 = *F1.Callees[0].NodePtr;
174bc0d697dSNico Weber ASSERT_EQ(F2.FId, 2);
175bc0d697dSNico Weber ASSERT_FALSE(F2.Callees.empty());
176bc0d697dSNico Weber
177bc0d697dSNico Weber const auto &F3 = *F2.Callees[0].NodePtr;
178bc0d697dSNico Weber ASSERT_EQ(F3.FId, 3);
179bc0d697dSNico Weber ASSERT_TRUE(F3.Callees.empty());
180bc0d697dSNico Weber
181bc0d697dSNico Weber // Now that we've established the preconditions, we check for specific aspects
182bc0d697dSNico Weber // of the nodes.
183bc0d697dSNico Weber EXPECT_EQ(F3.CallCount, 1u);
184bc0d697dSNico Weber EXPECT_EQ(F2.CallCount, 1u);
185bc0d697dSNico Weber EXPECT_EQ(F1.CallCount, 1u);
186bc0d697dSNico Weber EXPECT_EQ(F3.CumulativeLocalTime, 100u);
187bc0d697dSNico Weber EXPECT_EQ(F2.CumulativeLocalTime, 300u);
188bc0d697dSNico Weber EXPECT_EQ(F1.CumulativeLocalTime, 100u);
189bc0d697dSNico Weber }
190bc0d697dSNico Weber
TEST(FunctionCallTrieTest,DeepCallStack)191bc0d697dSNico Weber TEST(FunctionCallTrieTest, DeepCallStack) {
192bc0d697dSNico Weber // Simulate a relatively deep call stack (32 levels) and ensure that we can
193bc0d697dSNico Weber // properly pop all the way up the stack.
194bc0d697dSNico Weber profilingFlags()->setDefaults();
195bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
196bc0d697dSNico Weber FunctionCallTrie Trie(A);
197bc0d697dSNico Weber for (int i = 0; i < 32; ++i)
198bc0d697dSNico Weber Trie.enterFunction(i + 1, i, 0);
199bc0d697dSNico Weber Trie.exitFunction(1, 33, 0);
200bc0d697dSNico Weber
201bc0d697dSNico Weber // Here, validate that we have a 32-level deep function call path from the
202bc0d697dSNico Weber // root (1) down to the leaf (33).
203bc0d697dSNico Weber const auto &R = Trie.getRoots();
204bc0d697dSNico Weber ASSERT_EQ(R.size(), 1u);
205bc0d697dSNico Weber auto F = R[0];
206bc0d697dSNico Weber for (int i = 0; i < 32; ++i) {
207bc0d697dSNico Weber EXPECT_EQ(F->FId, i + 1);
208bc0d697dSNico Weber EXPECT_EQ(F->CallCount, 1u);
209bc0d697dSNico Weber if (F->Callees.empty() && i != 31)
210bc0d697dSNico Weber FAIL() << "Empty callees for FId " << F->FId;
211bc0d697dSNico Weber if (i != 31)
212bc0d697dSNico Weber F = F->Callees[0].NodePtr;
213bc0d697dSNico Weber }
214bc0d697dSNico Weber }
215bc0d697dSNico Weber
216bc0d697dSNico Weber // TODO: Test that we can handle cross-CPU migrations, where TSCs are not
217bc0d697dSNico Weber // guaranteed to be synchronised.
TEST(FunctionCallTrieTest,DeepCopy)218bc0d697dSNico Weber TEST(FunctionCallTrieTest, DeepCopy) {
219bc0d697dSNico Weber profilingFlags()->setDefaults();
220bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
221bc0d697dSNico Weber FunctionCallTrie Trie(A);
222bc0d697dSNico Weber
223bc0d697dSNico Weber Trie.enterFunction(1, 0, 0);
224bc0d697dSNico Weber Trie.enterFunction(2, 1, 0);
225bc0d697dSNico Weber Trie.exitFunction(2, 2, 0);
226bc0d697dSNico Weber Trie.enterFunction(3, 3, 0);
227bc0d697dSNico Weber Trie.exitFunction(3, 4, 0);
228bc0d697dSNico Weber Trie.exitFunction(1, 5, 0);
229bc0d697dSNico Weber
230bc0d697dSNico Weber // We want to make a deep copy and compare notes.
231bc0d697dSNico Weber auto B = FunctionCallTrie::InitAllocators();
232bc0d697dSNico Weber FunctionCallTrie Copy(B);
233bc0d697dSNico Weber Trie.deepCopyInto(Copy);
234bc0d697dSNico Weber
235bc0d697dSNico Weber ASSERT_NE(Trie.getRoots().size(), 0u);
236bc0d697dSNico Weber ASSERT_EQ(Trie.getRoots().size(), Copy.getRoots().size());
237bc0d697dSNico Weber const auto &R0Orig = *Trie.getRoots()[0];
238bc0d697dSNico Weber const auto &R0Copy = *Copy.getRoots()[0];
239bc0d697dSNico Weber EXPECT_EQ(R0Orig.FId, 1);
240bc0d697dSNico Weber EXPECT_EQ(R0Orig.FId, R0Copy.FId);
241bc0d697dSNico Weber
242bc0d697dSNico Weber ASSERT_EQ(R0Orig.Callees.size(), 2u);
243bc0d697dSNico Weber ASSERT_EQ(R0Copy.Callees.size(), 2u);
244bc0d697dSNico Weber
245bc0d697dSNico Weber const auto &F1Orig =
246bc0d697dSNico Weber *R0Orig.Callees
247bc0d697dSNico Weber .find_element(
248bc0d697dSNico Weber [](const FunctionCallTrie::NodeIdPair &R) { return R.FId == 2; })
249bc0d697dSNico Weber ->NodePtr;
250bc0d697dSNico Weber const auto &F1Copy =
251bc0d697dSNico Weber *R0Copy.Callees
252bc0d697dSNico Weber .find_element(
253bc0d697dSNico Weber [](const FunctionCallTrie::NodeIdPair &R) { return R.FId == 2; })
254bc0d697dSNico Weber ->NodePtr;
255bc0d697dSNico Weber EXPECT_EQ(&R0Orig, F1Orig.Parent);
256bc0d697dSNico Weber EXPECT_EQ(&R0Copy, F1Copy.Parent);
257bc0d697dSNico Weber }
258bc0d697dSNico Weber
TEST(FunctionCallTrieTest,MergeInto)259bc0d697dSNico Weber TEST(FunctionCallTrieTest, MergeInto) {
260bc0d697dSNico Weber profilingFlags()->setDefaults();
261bc0d697dSNico Weber auto A = FunctionCallTrie::InitAllocators();
262bc0d697dSNico Weber FunctionCallTrie T0(A);
263bc0d697dSNico Weber FunctionCallTrie T1(A);
264bc0d697dSNico Weber
265bc0d697dSNico Weber // 1 -> 2 -> 3
266bc0d697dSNico Weber T0.enterFunction(1, 0, 0);
267bc0d697dSNico Weber T0.enterFunction(2, 1, 0);
268bc0d697dSNico Weber T0.enterFunction(3, 2, 0);
269bc0d697dSNico Weber T0.exitFunction(3, 3, 0);
270bc0d697dSNico Weber T0.exitFunction(2, 4, 0);
271bc0d697dSNico Weber T0.exitFunction(1, 5, 0);
272bc0d697dSNico Weber
273bc0d697dSNico Weber // 1 -> 2 -> 3
274bc0d697dSNico Weber T1.enterFunction(1, 0, 0);
275bc0d697dSNico Weber T1.enterFunction(2, 1, 0);
276bc0d697dSNico Weber T1.enterFunction(3, 2, 0);
277bc0d697dSNico Weber T1.exitFunction(3, 3, 0);
278bc0d697dSNico Weber T1.exitFunction(2, 4, 0);
279bc0d697dSNico Weber T1.exitFunction(1, 5, 0);
280bc0d697dSNico Weber
281bc0d697dSNico Weber // We use a different allocator here to make sure that we're able to transfer
282bc0d697dSNico Weber // data into a FunctionCallTrie which uses a different allocator. This
283*a1e7e401SKazuaki Ishizaki // reflects the intended usage scenario for when we're collecting profiles
284*a1e7e401SKazuaki Ishizaki // that aggregate across threads.
285bc0d697dSNico Weber auto B = FunctionCallTrie::InitAllocators();
286bc0d697dSNico Weber FunctionCallTrie Merged(B);
287bc0d697dSNico Weber
288bc0d697dSNico Weber T0.mergeInto(Merged);
289bc0d697dSNico Weber T1.mergeInto(Merged);
290bc0d697dSNico Weber
291bc0d697dSNico Weber ASSERT_EQ(Merged.getRoots().size(), 1u);
292bc0d697dSNico Weber const auto &R0 = *Merged.getRoots()[0];
293bc0d697dSNico Weber EXPECT_EQ(R0.FId, 1);
294bc0d697dSNico Weber EXPECT_EQ(R0.CallCount, 2u);
295bc0d697dSNico Weber EXPECT_EQ(R0.CumulativeLocalTime, 10u);
296bc0d697dSNico Weber EXPECT_EQ(R0.Callees.size(), 1u);
297bc0d697dSNico Weber
298bc0d697dSNico Weber const auto &F1 = *R0.Callees[0].NodePtr;
299bc0d697dSNico Weber EXPECT_EQ(F1.FId, 2);
300bc0d697dSNico Weber EXPECT_EQ(F1.CallCount, 2u);
301bc0d697dSNico Weber EXPECT_EQ(F1.CumulativeLocalTime, 6u);
302bc0d697dSNico Weber EXPECT_EQ(F1.Callees.size(), 1u);
303bc0d697dSNico Weber
304bc0d697dSNico Weber const auto &F2 = *F1.Callees[0].NodePtr;
305bc0d697dSNico Weber EXPECT_EQ(F2.FId, 3);
306bc0d697dSNico Weber EXPECT_EQ(F2.CallCount, 2u);
307bc0d697dSNico Weber EXPECT_EQ(F2.CumulativeLocalTime, 2u);
308bc0d697dSNico Weber EXPECT_EQ(F2.Callees.size(), 0u);
309bc0d697dSNico Weber }
310bc0d697dSNico Weber
TEST(FunctionCallTrieTest,PlacementNewOnAlignedStorage)311bc0d697dSNico Weber TEST(FunctionCallTrieTest, PlacementNewOnAlignedStorage) {
312bc0d697dSNico Weber profilingFlags()->setDefaults();
313bc0d697dSNico Weber typename std::aligned_storage<sizeof(FunctionCallTrie::Allocators),
314bc0d697dSNico Weber alignof(FunctionCallTrie::Allocators)>::type
315bc0d697dSNico Weber AllocatorsStorage;
316bc0d697dSNico Weber new (&AllocatorsStorage)
317bc0d697dSNico Weber FunctionCallTrie::Allocators(FunctionCallTrie::InitAllocators());
318bc0d697dSNico Weber auto *A =
319bc0d697dSNico Weber reinterpret_cast<FunctionCallTrie::Allocators *>(&AllocatorsStorage);
320bc0d697dSNico Weber
321bc0d697dSNico Weber typename std::aligned_storage<sizeof(FunctionCallTrie),
322bc0d697dSNico Weber alignof(FunctionCallTrie)>::type FCTStorage;
323bc0d697dSNico Weber new (&FCTStorage) FunctionCallTrie(*A);
324bc0d697dSNico Weber auto *T = reinterpret_cast<FunctionCallTrie *>(&FCTStorage);
325bc0d697dSNico Weber
326bc0d697dSNico Weber // Put some data into it.
327bc0d697dSNico Weber T->enterFunction(1, 0, 0);
328bc0d697dSNico Weber T->exitFunction(1, 1, 0);
329bc0d697dSNico Weber
330bc0d697dSNico Weber // Re-initialize the objects in storage.
331bc0d697dSNico Weber T->~FunctionCallTrie();
332bc0d697dSNico Weber A->~Allocators();
333bc0d697dSNico Weber new (A) FunctionCallTrie::Allocators(FunctionCallTrie::InitAllocators());
334bc0d697dSNico Weber new (T) FunctionCallTrie(*A);
335bc0d697dSNico Weber
336bc0d697dSNico Weber // Then put some data into it again.
337bc0d697dSNico Weber T->enterFunction(1, 0, 0);
338bc0d697dSNico Weber T->exitFunction(1, 1, 0);
339bc0d697dSNico Weber }
340bc0d697dSNico Weber
341bc0d697dSNico Weber } // namespace
342bc0d697dSNico Weber
343bc0d697dSNico Weber } // namespace __xray
344