359 lines · cpp
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-exception6//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 23using namespace clang;24using namespace llvm;25 26namespace {27 28// Should be called before testing.29void setupProfiler() {30 timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, "test",31 /*TimeTraceVerbose=*/true);32}33 34// Should be called after `compileFromString()`.35// Returns profiler's JSON dump.36std::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.46bool compileFromString(StringRef Code, StringRef Standard, StringRef File,47 llvm::StringMap<std::string> Headers = {}) {48 49 auto FS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();50 FS->addFile(File, 0, MemoryBuffer::getMemBuffer(Code));51 for (const auto &Header : Headers) {52 FS->addFile(Header.getKey(), 0,53 MemoryBuffer::getMemBuffer(Header.getValue()));54 }55 56 auto Invocation = std::make_shared<CompilerInvocation>();57 std::vector<const char *> Args = {Standard.data(), File.data()};58 DiagnosticOptions InvocationDiagOpts;59 auto InvocationDiags =60 CompilerInstance::createDiagnostics(*FS, InvocationDiagOpts);61 CompilerInvocation::CreateFromArgs(*Invocation, Args, *InvocationDiags);62 63 CompilerInstance Compiler(std::move(Invocation));64 Compiler.setVirtualFileSystem(std::move(FS));65 Compiler.createDiagnostics();66 Compiler.createFileManager();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 78std::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.94std::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 the118 // synchronous events. Skip testing them.119 if (Name == "Source")120 continue;121 122 // This is a "summary" event, like "Total PerformPendingInstantiations",123 // skip it124 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 have135 // 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 llvm::stable_sort(Events, [](const auto &lhs, const auto &rhs) {139 return std::make_pair(lhs.TimestampBegin, -lhs.TimestampEnd) <140 std::make_pair(rhs.TimestampBegin, -rhs.TimestampEnd);141 });142 143 std::stringstream Stream;144 // Write a newline for better testing with multiline string literal.145 Stream << "\n";146 147 // Keep the current event stack.148 std::stack<const EventRecord *> EventStack;149 for (const auto &Event : Events) {150 // Pop every event in the stack until meeting the parent event.151 while (!EventStack.empty()) {152 bool InsideCurrentEvent =153 Event.TimestampBegin >= EventStack.top()->TimestampBegin &&154 Event.TimestampEnd <= EventStack.top()->TimestampEnd;155 156 // Presumably due to timer rounding, PerformPendingInstantiations often157 // appear to be within the timer interval of the immediately previous158 // event group. We always know these events occur at level 1, not level 2,159 // in our tests, so pop an event in that case.160 if (InsideCurrentEvent && Event.Name == "PerformPendingInstantiations" &&161 EventStack.size() == 2) {162 InsideCurrentEvent = false;163 }164 165 if (!InsideCurrentEvent)166 EventStack.pop();167 else168 break;169 }170 EventStack.push(&Event);171 172 // Write indentaion, name, detail, newline.173 for (size_t i = 1; i < EventStack.size(); ++i) {174 Stream << "| ";175 }176 Stream.write(Event.Name.data(), Event.Name.size());177 if (!Event.Metadata.empty()) {178 Stream << " (";179 Stream.write(Event.Metadata.data(), Event.Metadata.size());180 Stream << ")";181 }182 Stream << "\n";183 }184 return Stream.str();185}186 187} // namespace188 189// FIXME: Flaky test. See https://github.com/llvm/llvm-project/pull/138613190TEST(TimeProfilerTest, DISABLED_ConstantEvaluationCxx20) {191 std::string Code = R"(192void print(double value);193 194namespace slow_namespace {195 196consteval double slow_func() {197 double d = 0.0;198 for (int i = 0; i < 100; ++i) { // 8th line199 d += i; // 9th line200 }201 return d;202}203 204} // namespace slow_namespace205 206void slow_test() {207 constexpr auto slow_value = slow_namespace::slow_func(); // 17th line208 print(slow_namespace::slow_func()); // 18th line209 print(slow_value);210}211 212int slow_arr[12 + 34 * 56 + // 22nd line213 static_cast<int>(slow_namespace::slow_func())]; // 23rd line214 215constexpr int slow_init_list[] = {1, 1, 2, 3, 5, 8, 13, 21}; // 25th line216 )";217 218 setupProfiler();219 ASSERT_TRUE(compileFromString(Code, "-std=c++20", "test.cc"));220 std::string Json = teardownProfiler();221 ASSERT_EQ(R"(222Frontend (test.cc)223| ParseDeclarationOrFunctionDefinition (test.cc:2:1)224| ParseDeclarationOrFunctionDefinition (test.cc:6:1)225| | ParseFunctionDefinition (slow_func)226| | | EvaluateAsRValue (<test.cc:8:21>)227| | | EvaluateForOverflow (<test.cc:8:21, col:25>)228| | | EvaluateForOverflow (<test.cc:8:30, col:32>)229| | | EvaluateAsRValue (<test.cc:9:14>)230| | | EvaluateForOverflow (<test.cc:9:9, col:14>)231| | | isPotentialConstantExpr (slow_namespace::slow_func)232| | | EvaluateAsBooleanCondition (<test.cc:8:21, col:25>)233| | | | EvaluateAsRValue (<test.cc:8:21, col:25>)234| | | EvaluateAsBooleanCondition (<test.cc:8:21, col:25>)235| | | | EvaluateAsRValue (<test.cc:8:21, col:25>)236| ParseDeclarationOrFunctionDefinition (test.cc:16:1)237| | ParseFunctionDefinition (slow_test)238| | | EvaluateAsInitializer (slow_value)239| | | EvaluateAsConstantExpr (<test.cc:17:33, col:59>)240| | | EvaluateAsConstantExpr (<test.cc:18:11, col:37>)241| ParseDeclarationOrFunctionDefinition (test.cc:22:1)242| | EvaluateAsConstantExpr (<test.cc:23:31, col:57>)243| | EvaluateAsRValue (<test.cc:22:14, line:23:58>)244| ParseDeclarationOrFunctionDefinition (test.cc:25:1)245| | EvaluateAsInitializer (slow_init_list)246| PerformPendingInstantiations247)",248 buildTraceGraph(Json));249}250 251TEST(TimeProfilerTest, ClassTemplateInstantiations) {252 std::string Code = R"(253 template<class T>254 struct S255 {256 void foo() {}257 void bar();258 };259 260 template struct S<double>; // explicit instantiation of S<double>261 262 void user() {263 S<int> a; // implicit instantiation of S<int>264 S<float>* b;265 b->foo(); // implicit instatiation of S<float> and S<float>::foo()266 }267 )";268 269 setupProfiler();270 ASSERT_TRUE(compileFromString(Code, "-std=c++20", "test.cc"));271 std::string Json = teardownProfiler();272 ASSERT_EQ(R"(273Frontend (test.cc)274| ParseClass (S)275| InstantiateClass (S<double>, test.cc:9)276| InstantiateFunction (S<double>::foo, test.cc:5)277| ParseDeclarationOrFunctionDefinition (test.cc:11:5)278| | ParseFunctionDefinition (user)279| | | InstantiateClass (S<int>, test.cc:3)280| | | InstantiateClass (S<float>, test.cc:3)281| | | DeferInstantiation (S<float>::foo)282| PerformPendingInstantiations283| | InstantiateFunction (S<float>::foo, test.cc:5)284)",285 buildTraceGraph(Json));286}287 288TEST(TimeProfilerTest, TemplateInstantiations) {289 std::string B_H = R"(290 template <typename T>291 T fooC(T t) {292 return T();293 }294 295 template <typename T>296 constexpr T fooB(T t) {297 return fooC(t);298 }299 300 #define MacroTemp(x) template <typename T> void foo##x(T) { T(); }301 )";302 303 std::string A_H = R"(304 #include "b.h"305 306 MacroTemp(MTA)307 308 template <typename T>309 void fooA(T t) { fooB(t); fooMTA(t); }310 )";311 std::string Code = R"(312 #include "a.h"313 void user() { fooA(0); }314 )";315 316 setupProfiler();317 ASSERT_TRUE(compileFromString(Code, "-std=c++20", "test.cc",318 /*Headers=*/{{"a.h", A_H}, {"b.h", B_H}}));319 std::string Json = teardownProfiler();320 ASSERT_EQ(R"(321Frontend (test.cc)322| ParseFunctionDefinition (fooC)323| ParseFunctionDefinition (fooB)324| ParseFunctionDefinition (fooMTA)325| ParseFunctionDefinition (fooA)326| ParseDeclarationOrFunctionDefinition (test.cc:3:5)327| | ParseFunctionDefinition (user)328| | | DeferInstantiation (fooA<int>)329| PerformPendingInstantiations330| | InstantiateFunction (fooA<int>, a.h:7)331| | | InstantiateFunction (fooB<int>, b.h:8)332| | | | DeferInstantiation (fooC<int>)333| | | DeferInstantiation (fooMTA<int>)334| | | InstantiateFunction (fooC<int>, b.h:3)335| | | InstantiateFunction (fooMTA<int>, a.h:4)336)",337 buildTraceGraph(Json));338}339 340TEST(TimeProfilerTest, ConstantEvaluationC99) {341 std::string Code = R"(342struct {343 short quantval[4]; // 3rd line344} value;345 )";346 347 setupProfiler();348 ASSERT_TRUE(compileFromString(Code, "-std=c99", "test.c"));349 std::string Json = teardownProfiler();350 ASSERT_EQ(R"(351Frontend (test.c)352| ParseDeclarationOrFunctionDefinition (test.c:2:1)353| | isIntegerConstantExpr (<test.c:3:18>)354| | EvaluateKnownConstIntCheckOverflow (<test.c:3:18>)355| PerformPendingInstantiations356)",357 buildTraceGraph(Json));358}359