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 | |
18 | using namespace clang; |
19 | using namespace llvm; |
20 | |
21 | namespace { |
22 | |
23 | // Should be called before testing. |
24 | void setupProfiler() { |
25 | timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, ProcName: "test" ); |
26 | } |
27 | |
28 | // Should be called after `compileFromString()`. |
29 | // Returns profiler's JSON dump. |
30 | std::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. |
40 | bool 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. |
63 | std::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 | |
146 | TEST(TimeProfilerTest, ConstantEvaluationCxx20) { |
147 | constexpr StringRef Code = R"( |
148 | void print(double value); |
149 | |
150 | namespace slow_namespace { |
151 | |
152 | consteval 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 | |
162 | void 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 | |
168 | int slow_arr[12 + 34 * 56 + // 22nd line |
169 | static_cast<int>(slow_namespace::slow_func())]; // 23rd line |
170 | |
171 | constexpr 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"( |
179 | Frontend |
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 | |
210 | TEST(TimeProfilerTest, ConstantEvaluationC99) { |
211 | constexpr StringRef Code = R"( |
212 | struct { |
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"( |
222 | Frontend |
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 | |