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