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/Support/JSON.h"
14#include "llvm/Support/TimeProfiler.h"
15
16#include "gtest/gtest.h"
17
18using namespace clang;
19using namespace llvm;
20
21namespace {
22
23// Should be called before testing.
24void setupProfiler() {
25 timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, ProcName: "test");
26}
27
28// Should be called after `compileFromString()`.
29// Returns profiler's JSON dump.
30std::string teardownProfiler() {
31 SmallVector<char, 1024> SmallVec;
32 raw_svector_ostream OS(SmallVec);
33 timeTraceProfilerWrite(OS);
34 timeTraceProfilerCleanup();
35 return OS.str().str();
36}
37
38// Returns true if code compiles successfully.
39// We only parse AST here. This is enough for constexpr evaluation.
40bool compileFromString(StringRef Code, StringRef Standard, StringRef FileName) {
41 CompilerInstance Compiler;
42 Compiler.createDiagnostics();
43
44 auto Invocation = std::make_shared<CompilerInvocation>();
45 Invocation->getPreprocessorOpts().addRemappedFile(
46 From: FileName, To: MemoryBuffer::getMemBuffer(InputData: Code).release());
47 const char *Args[] = {Standard.data(), FileName.data()};
48 CompilerInvocation::CreateFromArgs(Res&: *Invocation, CommandLineArgs: Args,
49 Diags&: Compiler.getDiagnostics());
50 Compiler.setInvocation(std::move(Invocation));
51
52 class TestFrontendAction : public ASTFrontendAction {
53 private:
54 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
55 StringRef InFile) override {
56 return std::make_unique<ASTConsumer>();
57 }
58 } Action;
59 return Compiler.ExecuteAction(Act&: Action);
60}
61
62// Returns pretty-printed trace graph.
63std::string buildTraceGraph(StringRef Json) {
64 struct EventRecord {
65 int64_t TimestampBegin;
66 int64_t TimestampEnd;
67 StringRef Name;
68 StringRef Detail;
69 };
70 std::vector<EventRecord> Events;
71
72 // Parse `EventRecord`s from JSON dump.
73 Expected<json::Value> Root = json::parse(JSON: Json);
74 if (!Root)
75 return "";
76 for (json::Value &TraceEventValue :
77 *Root->getAsObject()->getArray(K: "traceEvents")) {
78 json::Object *TraceEventObj = TraceEventValue.getAsObject();
79
80 int64_t TimestampBegin = TraceEventObj->getInteger(K: "ts").value_or(u: 0);
81 int64_t TimestampEnd =
82 TimestampBegin + TraceEventObj->getInteger(K: "dur").value_or(u: 0);
83 StringRef Name = TraceEventObj->getString(K: "name").value_or(u: "");
84 StringRef Detail = "";
85 if (json::Object *Args = TraceEventObj->getObject(K: "args"))
86 Detail = Args->getString(K: "detail").value_or(u: "");
87
88 // This is a "summary" event, like "Total PerformPendingInstantiations",
89 // skip it
90 if (TimestampBegin == 0)
91 continue;
92
93 Events.emplace_back(
94 args: EventRecord{.TimestampBegin: TimestampBegin, .TimestampEnd: TimestampEnd, .Name: Name, .Detail: Detail});
95 }
96
97 // There can be nested events that are very fast, for example:
98 // {"name":"EvaluateAsBooleanCondition",... ,"ts":2380,"dur":1}
99 // {"name":"EvaluateAsRValue",... ,"ts":2380,"dur":1}
100 // Therefore we should reverse the events list, so that events that have
101 // started earlier are first in the list.
102 // Then do a stable sort, we need it for the trace graph.
103 std::reverse(first: Events.begin(), last: Events.end());
104 std::stable_sort(
105 first: Events.begin(), last: Events.end(), comp: [](const auto &lhs, const auto &rhs) {
106 return std::make_pair(lhs.TimestampBegin, -lhs.TimestampEnd) <
107 std::make_pair(rhs.TimestampBegin, -rhs.TimestampEnd);
108 });
109
110 std::stringstream Stream;
111 // Write a newline for better testing with multiline string literal.
112 Stream << "\n";
113
114 // Keep the current event stack.
115 std::stack<const EventRecord *> EventStack;
116 for (const auto &Event : Events) {
117 // Pop every event in the stack until meeting the parent event.
118 while (!EventStack.empty()) {
119 bool InsideCurrentEvent =
120 Event.TimestampBegin >= EventStack.top()->TimestampBegin &&
121 Event.TimestampEnd <= EventStack.top()->TimestampEnd;
122 if (!InsideCurrentEvent)
123 EventStack.pop();
124 else
125 break;
126 }
127 EventStack.push(x: &Event);
128
129 // Write indentaion, name, detail, newline.
130 for (size_t i = 1; i < EventStack.size(); ++i) {
131 Stream << "| ";
132 }
133 Stream.write(s: Event.Name.data(), n: Event.Name.size());
134 if (!Event.Detail.empty()) {
135 Stream << " (";
136 Stream.write(s: Event.Detail.data(), n: Event.Detail.size());
137 Stream << ")";
138 }
139 Stream << "\n";
140 }
141 return Stream.str();
142}
143
144} // namespace
145
146TEST(TimeProfilerTest, ConstantEvaluationCxx20) {
147 constexpr StringRef Code = R"(
148void print(double value);
149
150namespace slow_namespace {
151
152consteval double slow_func() {
153 double d = 0.0;
154 for (int i = 0; i < 100; ++i) { // 8th line
155 d += i; // 9th line
156 }
157 return d;
158}
159
160} // namespace slow_namespace
161
162void slow_test() {
163 constexpr auto slow_value = slow_namespace::slow_func(); // 17th line
164 print(slow_namespace::slow_func()); // 18th line
165 print(slow_value);
166}
167
168int slow_arr[12 + 34 * 56 + // 22nd line
169 static_cast<int>(slow_namespace::slow_func())]; // 23rd line
170
171constexpr int slow_init_list[] = {1, 1, 2, 3, 5, 8, 13, 21}; // 25th line
172 )";
173
174 setupProfiler();
175 ASSERT_TRUE(compileFromString(Code, "-std=c++20", "test.cc"));
176 std::string Json = teardownProfiler();
177 std::string TraceGraph = buildTraceGraph(Json);
178 ASSERT_TRUE(TraceGraph == R"(
179Frontend
180| ParseDeclarationOrFunctionDefinition (test.cc:2:1)
181| ParseDeclarationOrFunctionDefinition (test.cc:6:1)
182| | ParseFunctionDefinition (slow_func)
183| | | EvaluateAsRValue (<test.cc:8:21>)
184| | | EvaluateForOverflow (<test.cc:8:21, col:25>)
185| | | EvaluateForOverflow (<test.cc:8:30, col:32>)
186| | | EvaluateAsRValue (<test.cc:9:14>)
187| | | EvaluateForOverflow (<test.cc:9:9, col:14>)
188| | | isPotentialConstantExpr (slow_namespace::slow_func)
189| | | EvaluateAsBooleanCondition (<test.cc:8:21, col:25>)
190| | | | EvaluateAsRValue (<test.cc:8:21, col:25>)
191| | | EvaluateAsBooleanCondition (<test.cc:8:21, col:25>)
192| | | | EvaluateAsRValue (<test.cc:8:21, col:25>)
193| ParseDeclarationOrFunctionDefinition (test.cc:16:1)
194| | ParseFunctionDefinition (slow_test)
195| | | EvaluateAsInitializer (slow_value)
196| | | EvaluateAsConstantExpr (<test.cc:17:33, col:59>)
197| | | EvaluateAsConstantExpr (<test.cc:18:11, col:37>)
198| ParseDeclarationOrFunctionDefinition (test.cc:22:1)
199| | EvaluateAsConstantExpr (<test.cc:23:31, col:57>)
200| | EvaluateAsRValue (<test.cc:22:14, line:23:58>)
201| ParseDeclarationOrFunctionDefinition (test.cc:25:1)
202| | EvaluateAsInitializer (slow_init_list)
203| PerformPendingInstantiations
204)");
205
206 // NOTE: If this test is failing, run this test with
207 // `llvm::errs() << TraceGraph;` and change the assert above.
208}
209
210TEST(TimeProfilerTest, ConstantEvaluationC99) {
211 constexpr StringRef Code = R"(
212struct {
213 short quantval[4]; // 3rd line
214} value;
215 )";
216
217 setupProfiler();
218 ASSERT_TRUE(compileFromString(Code, "-std=c99", "test.c"));
219 std::string Json = teardownProfiler();
220 std::string TraceGraph = buildTraceGraph(Json);
221 ASSERT_TRUE(TraceGraph == R"(
222Frontend
223| ParseDeclarationOrFunctionDefinition (test.c:2:1)
224| | isIntegerConstantExpr (<test.c:3:18>)
225| | EvaluateKnownConstIntCheckOverflow (<test.c:3:18>)
226| PerformPendingInstantiations
227)");
228
229 // NOTE: If this test is failing, run this test with
230 // `llvm::errs() << TraceGraph;` and change the assert above.
231}
232

source code of clang/unittests/Support/TimeProfilerTest.cpp