1 //===- unittests/Support/TimeProfilerTest.cpp -----------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "clang/Frontend/CompilerInstance.h"
10 #include "clang/Frontend/FrontendActions.h"
11 #include "clang/Lex/PreprocessorOptions.h"
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"
20 #include "gtest/gtest.h"
23 using namespace clang
;
28 // Should be called before testing.
29 void setupProfiler() {
30 timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, "test",
31 /*TimeTraceVerbose=*/true);
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();
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
= {}) {
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()));
56 llvm::IntrusiveRefCntPtr
<FileManager
> Files(
57 new FileManager(FileSystemOptions(), FS
));
58 CompilerInstance Compiler
;
59 Compiler
.createDiagnostics(Files
->getVirtualFileSystem());
60 Compiler
.setFileManager(Files
.get());
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
));
68 class TestFrontendAction
: public ASTFrontendAction
{
70 std::unique_ptr
<ASTConsumer
> CreateASTConsumer(CompilerInstance
&CI
,
71 StringRef InFile
) override
{
72 return std::make_unique
<ASTConsumer
>();
75 return Compiler
.ExecuteAction(Action
);
78 std::string
GetMetadata(json::Object
*Event
) {
80 llvm::raw_string_ostream
OS(M
);
81 if (json::Object
*Args
= Event
->getObject("args")) {
82 if (auto Detail
= Args
->getString("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"))
93 // Returns pretty-printed trace graph.
94 std::string
buildTraceGraph(StringRef Json
) {
96 int64_t TimestampBegin
;
101 std::vector
<EventRecord
> Events
;
103 // Parse `EventRecord`s from JSON dump.
104 Expected
<json::Value
> Root
= json::parse(Json
);
107 for (json::Value
&TraceEventValue
:
108 *Root
->getAsObject()->getArray("traceEvents")) {
109 json::Object
*TraceEventObj
= TraceEventValue
.getAsObject();
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
);
117 // Source events are asynchronous events and may not perfectly nest the
118 // synchronous events. Skip testing them.
119 if (Name
== "Source")
122 // This is a "summary" event, like "Total PerformPendingInstantiations",
124 if (TimestampBegin
== 0)
128 EventRecord
{TimestampBegin
, TimestampEnd
, Name
, Metadata
});
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());
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
);
144 std::stringstream Stream
;
145 // Write a newline for better testing with multiline string literal.
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
)
161 EventStack
.push(&Event
);
163 // Write indentaion, name, detail, newline.
164 for (size_t i
= 1; i
< EventStack
.size(); ++i
) {
167 Stream
.write(Event
.Name
.data(), Event
.Name
.size());
168 if (!Event
.Metadata
.empty()) {
170 Stream
.write(Event
.Metadata
.data(), Event
.Metadata
.size());
180 TEST(TimeProfilerTest
, ConstantEvaluationCxx20
) {
181 std::string Code
= R
"(
182 void print(double value);
184 namespace slow_namespace {
186 consteval double slow_func() {
188 for (int i = 0; i < 100; ++i) { // 8th line
194 } // namespace slow_namespace
197 constexpr auto slow_value = slow_namespace::slow_func(); // 17th line
198 print(slow_namespace::slow_func()); // 18th line
202 int slow_arr[12 + 34 * 56 + // 22nd line
203 static_cast<int>(slow_namespace::slow_func())]; // 23rd line
205 constexpr int slow_init_list[] = {1, 1, 2, 3, 5, 8, 13, 21}; // 25th line
209 ASSERT_TRUE(compileFromString(Code
, "-std=c++20", "test.cc"));
210 std::string Json
= teardownProfiler();
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
238 buildTraceGraph(Json
));
241 TEST(TimeProfilerTest
, ClassTemplateInstantiations
) {
242 std::string Code
= R
"(
250 template struct S<double>; // explicit instantiation of S<double>
253 S<int> a; // implicit instantiation of S<int>
255 b->foo(); // implicit instatiation of S<float> and S<float>::foo()
260 ASSERT_TRUE(compileFromString(Code
, "-std=c++20", "test.cc"));
261 std::string Json
= teardownProfiler();
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)
275 buildTraceGraph(Json
));
278 TEST(TimeProfilerTest
, TemplateInstantiations
) {
279 std::string B_H
= R
"(
280 template <typename T>
285 template <typename T>
286 constexpr T fooB(T t) {
290 #define MacroTemp(x) template <typename T> void foo##x(T) { T(); }
293 std::string A_H
= R
"(
298 template <typename T>
299 void fooA(T t) { fooB(t); fooMTA(t); }
301 std::string Code
= R
"(
303 void user() { fooA(0); }
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();
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)
327 buildTraceGraph(Json
));
330 TEST(TimeProfilerTest
, ConstantEvaluationC99
) {
331 std::string Code
= R
"(
333 short quantval[4]; // 3rd line
338 ASSERT_TRUE(compileFromString(Code
, "-std=c99", "test.c"));
339 std::string Json
= teardownProfiler();
342 | ParseDeclarationOrFunctionDefinition (test.c:2:1)
343 | | isIntegerConstantExpr (<test.c:3:18>)
344 | | EvaluateKnownConstIntCheckOverflow (<test.c:3:18>)
345 | PerformPendingInstantiations
347 buildTraceGraph(Json
));