xref: /llvm-project/clang/unittests/Support/TimeProfilerTest.cpp (revision df9a14d7bbf1180e4f1474254c9d7ed6bcb4ce55)
1 //===- unittests/Support/TimeProfilerTest.cpp -----------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "clang/Frontend/CompilerInstance.h"
10 #include "clang/Frontend/FrontendActions.h"
11 #include "clang/Lex/PreprocessorOptions.h"
12 
13 #include "llvm/ADT/StringMap.h"
14 #include "llvm/Support/JSON.h"
15 #include "llvm/Support/Path.h"
16 #include "llvm/Support/TimeProfiler.h"
17 #include "llvm/Support/VirtualFileSystem.h"
18 #include <stack>
19 
20 #include "gtest/gtest.h"
21 #include <tuple>
22 
23 using namespace clang;
24 using namespace llvm;
25 
26 namespace {
27 
28 // Should be called before testing.
29 void setupProfiler() {
30   timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, "test",
31                               /*TimeTraceVerbose=*/true);
32 }
33 
34 // Should be called after `compileFromString()`.
35 // Returns profiler's JSON dump.
36 std::string teardownProfiler() {
37   SmallVector<char, 1024> SmallVec;
38   raw_svector_ostream OS(SmallVec);
39   timeTraceProfilerWrite(OS);
40   timeTraceProfilerCleanup();
41   return OS.str().str();
42 }
43 
44 // Returns true if code compiles successfully.
45 // We only parse AST here. This is enough for constexpr evaluation.
46 bool compileFromString(StringRef Code, StringRef Standard, StringRef File,
47                        llvm::StringMap<std::string> Headers = {}) {
48 
49   llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS(
50       new llvm::vfs::InMemoryFileSystem());
51   FS->addFile(File, 0, MemoryBuffer::getMemBuffer(Code));
52   for (const auto &Header : Headers) {
53     FS->addFile(Header.getKey(), 0,
54                 MemoryBuffer::getMemBuffer(Header.getValue()));
55   }
56   llvm::IntrusiveRefCntPtr<FileManager> Files(
57       new FileManager(FileSystemOptions(), FS));
58   CompilerInstance Compiler;
59   Compiler.createDiagnostics(Files->getVirtualFileSystem());
60   Compiler.setFileManager(Files.get());
61 
62   auto Invocation = std::make_shared<CompilerInvocation>();
63   std::vector<const char *> Args = {Standard.data(), File.data()};
64   CompilerInvocation::CreateFromArgs(*Invocation, Args,
65                                      Compiler.getDiagnostics());
66   Compiler.setInvocation(std::move(Invocation));
67 
68   class TestFrontendAction : public ASTFrontendAction {
69   private:
70     std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
71                                                    StringRef InFile) override {
72       return std::make_unique<ASTConsumer>();
73     }
74   } Action;
75   return Compiler.ExecuteAction(Action);
76 }
77 
78 std::string GetMetadata(json::Object *Event) {
79   std::string M;
80   llvm::raw_string_ostream OS(M);
81   if (json::Object *Args = Event->getObject("args")) {
82     if (auto Detail = Args->getString("detail"))
83       OS << Detail;
84     // Use only filename to not include os-specific path separators.
85     if (auto File = Args->getString("file"))
86       OS << (M.empty() ? "" : ", ") << llvm::sys::path::filename(*File);
87     if (auto Line = Args->getInteger("line"))
88       OS << ":" << *Line;
89   }
90   return M;
91 }
92 
93 // Returns pretty-printed trace graph.
94 std::string buildTraceGraph(StringRef Json) {
95   struct EventRecord {
96     int64_t TimestampBegin;
97     int64_t TimestampEnd;
98     std::string Name;
99     std::string Metadata;
100   };
101   std::vector<EventRecord> Events;
102 
103   // Parse `EventRecord`s from JSON dump.
104   Expected<json::Value> Root = json::parse(Json);
105   if (!Root)
106     return "";
107   for (json::Value &TraceEventValue :
108        *Root->getAsObject()->getArray("traceEvents")) {
109     json::Object *TraceEventObj = TraceEventValue.getAsObject();
110 
111     int64_t TimestampBegin = TraceEventObj->getInteger("ts").value_or(0);
112     int64_t TimestampEnd =
113         TimestampBegin + TraceEventObj->getInteger("dur").value_or(0);
114     std::string Name = TraceEventObj->getString("name").value_or("").str();
115     std::string Metadata = GetMetadata(TraceEventObj);
116 
117     // Source events are asynchronous events and may not perfectly nest the
118     // synchronous events. Skip testing them.
119     if (Name == "Source")
120       continue;
121 
122     // This is a "summary" event, like "Total PerformPendingInstantiations",
123     // skip it
124     if (TimestampBegin == 0)
125       continue;
126 
127     Events.emplace_back(
128         EventRecord{TimestampBegin, TimestampEnd, Name, Metadata});
129   }
130 
131   // There can be nested events that are very fast, for example:
132   // {"name":"EvaluateAsBooleanCondition",... ,"ts":2380,"dur":1}
133   // {"name":"EvaluateAsRValue",... ,"ts":2380,"dur":1}
134   // Therefore we should reverse the events list, so that events that have
135   // started earlier are first in the list.
136   // Then do a stable sort, we need it for the trace graph.
137   std::reverse(Events.begin(), Events.end());
138   std::stable_sort(
139       Events.begin(), Events.end(), [](const auto &lhs, const auto &rhs) {
140         return std::make_pair(lhs.TimestampBegin, -lhs.TimestampEnd) <
141                std::make_pair(rhs.TimestampBegin, -rhs.TimestampEnd);
142       });
143 
144   std::stringstream Stream;
145   // Write a newline for better testing with multiline string literal.
146   Stream << "\n";
147 
148   // Keep the current event stack.
149   std::stack<const EventRecord *> EventStack;
150   for (const auto &Event : Events) {
151     // Pop every event in the stack until meeting the parent event.
152     while (!EventStack.empty()) {
153       bool InsideCurrentEvent =
154           Event.TimestampBegin >= EventStack.top()->TimestampBegin &&
155           Event.TimestampEnd <= EventStack.top()->TimestampEnd;
156       if (!InsideCurrentEvent)
157         EventStack.pop();
158       else
159         break;
160     }
161     EventStack.push(&Event);
162 
163     // Write indentaion, name, detail, newline.
164     for (size_t i = 1; i < EventStack.size(); ++i) {
165       Stream << "| ";
166     }
167     Stream.write(Event.Name.data(), Event.Name.size());
168     if (!Event.Metadata.empty()) {
169       Stream << " (";
170       Stream.write(Event.Metadata.data(), Event.Metadata.size());
171       Stream << ")";
172     }
173     Stream << "\n";
174   }
175   return Stream.str();
176 }
177 
178 } // namespace
179 
180 TEST(TimeProfilerTest, ConstantEvaluationCxx20) {
181   std::string Code = R"(
182 void print(double value);
183 
184 namespace slow_namespace {
185 
186 consteval double slow_func() {
187     double d = 0.0;
188     for (int i = 0; i < 100; ++i) { // 8th line
189         d += i;                     // 9th line
190     }
191     return d;
192 }
193 
194 } // namespace slow_namespace
195 
196 void slow_test() {
197     constexpr auto slow_value = slow_namespace::slow_func(); // 17th line
198     print(slow_namespace::slow_func());                      // 18th line
199     print(slow_value);
200 }
201 
202 int slow_arr[12 + 34 * 56 +                                  // 22nd line
203              static_cast<int>(slow_namespace::slow_func())]; // 23rd line
204 
205 constexpr int slow_init_list[] = {1, 1, 2, 3, 5, 8, 13, 21}; // 25th line
206     )";
207 
208   setupProfiler();
209   ASSERT_TRUE(compileFromString(Code, "-std=c++20", "test.cc"));
210   std::string Json = teardownProfiler();
211   ASSERT_EQ(R"(
212 Frontend (test.cc)
213 | ParseDeclarationOrFunctionDefinition (test.cc:2:1)
214 | ParseDeclarationOrFunctionDefinition (test.cc:6:1)
215 | | ParseFunctionDefinition (slow_func)
216 | | | EvaluateAsRValue (<test.cc:8:21>)
217 | | | EvaluateForOverflow (<test.cc:8:21, col:25>)
218 | | | EvaluateForOverflow (<test.cc:8:30, col:32>)
219 | | | EvaluateAsRValue (<test.cc:9:14>)
220 | | | EvaluateForOverflow (<test.cc:9:9, col:14>)
221 | | | isPotentialConstantExpr (slow_namespace::slow_func)
222 | | | EvaluateAsBooleanCondition (<test.cc:8:21, col:25>)
223 | | | | EvaluateAsRValue (<test.cc:8:21, col:25>)
224 | | | EvaluateAsBooleanCondition (<test.cc:8:21, col:25>)
225 | | | | EvaluateAsRValue (<test.cc:8:21, col:25>)
226 | ParseDeclarationOrFunctionDefinition (test.cc:16:1)
227 | | ParseFunctionDefinition (slow_test)
228 | | | EvaluateAsInitializer (slow_value)
229 | | | EvaluateAsConstantExpr (<test.cc:17:33, col:59>)
230 | | | EvaluateAsConstantExpr (<test.cc:18:11, col:37>)
231 | ParseDeclarationOrFunctionDefinition (test.cc:22:1)
232 | | EvaluateAsConstantExpr (<test.cc:23:31, col:57>)
233 | | EvaluateAsRValue (<test.cc:22:14, line:23:58>)
234 | ParseDeclarationOrFunctionDefinition (test.cc:25:1)
235 | | EvaluateAsInitializer (slow_init_list)
236 | PerformPendingInstantiations
237 )",
238             buildTraceGraph(Json));
239 }
240 
241 TEST(TimeProfilerTest, ClassTemplateInstantiations) {
242   std::string Code = R"(
243     template<class T>
244     struct S
245     {
246       void foo() {}
247       void bar();
248     };
249 
250     template struct S<double>; // explicit instantiation of S<double>
251 
252     void user() {
253       S<int> a; // implicit instantiation of S<int>
254       S<float>* b;
255       b->foo(); // implicit instatiation of S<float> and S<float>::foo()
256     }
257   )";
258 
259   setupProfiler();
260   ASSERT_TRUE(compileFromString(Code, "-std=c++20", "test.cc"));
261   std::string Json = teardownProfiler();
262   ASSERT_EQ(R"(
263 Frontend (test.cc)
264 | ParseClass (S)
265 | InstantiateClass (S<double>, test.cc:9)
266 | InstantiateFunction (S<double>::foo, test.cc:5)
267 | ParseDeclarationOrFunctionDefinition (test.cc:11:5)
268 | | ParseFunctionDefinition (user)
269 | | | InstantiateClass (S<int>, test.cc:3)
270 | | | InstantiateClass (S<float>, test.cc:3)
271 | | | DeferInstantiation (S<float>::foo)
272 | PerformPendingInstantiations
273 | | InstantiateFunction (S<float>::foo, test.cc:5)
274 )",
275             buildTraceGraph(Json));
276 }
277 
278 TEST(TimeProfilerTest, TemplateInstantiations) {
279   std::string B_H = R"(
280     template <typename T>
281     T fooC(T t) {
282       return T();
283     }
284 
285     template <typename T>
286     constexpr T fooB(T t) {
287       return fooC(t);
288     }
289 
290     #define MacroTemp(x) template <typename T> void foo##x(T) { T(); }
291   )";
292 
293   std::string A_H = R"(
294     #include "b.h"
295 
296     MacroTemp(MTA)
297 
298     template <typename T>
299     void fooA(T t) { fooB(t); fooMTA(t); }
300   )";
301   std::string Code = R"(
302     #include "a.h"
303     void user() { fooA(0); }
304   )";
305 
306   setupProfiler();
307   ASSERT_TRUE(compileFromString(Code, "-std=c++20", "test.cc",
308                                 /*Headers=*/{{"a.h", A_H}, {"b.h", B_H}}));
309   std::string Json = teardownProfiler();
310   ASSERT_EQ(R"(
311 Frontend (test.cc)
312 | ParseFunctionDefinition (fooC)
313 | ParseFunctionDefinition (fooB)
314 | ParseFunctionDefinition (fooMTA)
315 | ParseFunctionDefinition (fooA)
316 | ParseDeclarationOrFunctionDefinition (test.cc:3:5)
317 | | ParseFunctionDefinition (user)
318 | | | DeferInstantiation (fooA<int>)
319 | PerformPendingInstantiations
320 | | InstantiateFunction (fooA<int>, a.h:7)
321 | | | InstantiateFunction (fooB<int>, b.h:8)
322 | | | | DeferInstantiation (fooC<int>)
323 | | | DeferInstantiation (fooMTA<int>)
324 | | | InstantiateFunction (fooC<int>, b.h:3)
325 | | | InstantiateFunction (fooMTA<int>, a.h:4)
326 )",
327             buildTraceGraph(Json));
328 }
329 
330 TEST(TimeProfilerTest, ConstantEvaluationC99) {
331   std::string Code = R"(
332 struct {
333   short quantval[4]; // 3rd line
334 } value;
335     )";
336 
337   setupProfiler();
338   ASSERT_TRUE(compileFromString(Code, "-std=c99", "test.c"));
339   std::string Json = teardownProfiler();
340   ASSERT_EQ(R"(
341 Frontend (test.c)
342 | ParseDeclarationOrFunctionDefinition (test.c:2:1)
343 | | isIntegerConstantExpr (<test.c:3:18>)
344 | | EvaluateKnownConstIntCheckOverflow (<test.c:3:18>)
345 | PerformPendingInstantiations
346 )",
347             buildTraceGraph(Json));
348 }
349