diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -56,6 +56,7 @@ #include "llvm/ADT/SmallBitVector.h" #include "llvm/Support/Debug.h" #include "llvm/Support/SaveAndRestore.h" +#include "llvm/Support/TimeProfiler.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -660,6 +661,19 @@ CallStackFrame &Frame; const LValue *OldThis; }; + + // A shorthand time trace scope struct, prints source range, for example + // {"name":"EvaluateAsRValue","args":{"detail":""}}} + class ExprTimeTraceScope { + public: + ExprTimeTraceScope(const Expr *E, const ASTContext &Ctx, StringRef Name) + : TimeScope(Name, [E, &Ctx] { + return E->getSourceRange().printToString(Ctx.getSourceManager()); + }) {} + + private: + llvm::TimeTraceScope TimeScope; + }; } static bool HandleDestruction(EvalInfo &Info, const Expr *E, @@ -15093,6 +15107,7 @@ bool InConstantContext) const { assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsRValue"); EvalInfo Info(Ctx, Result, EvalInfo::EM_IgnoreSideEffects); Info.InConstantContext = InConstantContext; return ::EvaluateAsRValue(this, Result, Ctx, Info); @@ -15102,6 +15117,7 @@ bool InConstantContext) const { assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsBooleanCondition"); EvalResult Scratch; return EvaluateAsRValue(Scratch, Ctx, InConstantContext) && HandleConversionToBool(Scratch.Val, Result); @@ -15112,6 +15128,7 @@ bool InConstantContext) const { assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsInt"); EvalInfo Info(Ctx, Result, EvalInfo::EM_IgnoreSideEffects); Info.InConstantContext = InConstantContext; return ::EvaluateAsInt(this, Result, Ctx, AllowSideEffects, Info); @@ -15122,6 +15139,7 @@ bool InConstantContext) const { assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsFixedPoint"); EvalInfo Info(Ctx, Result, EvalInfo::EM_IgnoreSideEffects); Info.InConstantContext = InConstantContext; return ::EvaluateAsFixedPoint(this, Result, Ctx, AllowSideEffects, Info); @@ -15136,6 +15154,7 @@ if (!getType()->isRealFloatingType()) return false; + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsFloat"); EvalResult ExprResult; if (!EvaluateAsRValue(ExprResult, Ctx, InConstantContext) || !ExprResult.Val.isFloat() || @@ -15151,6 +15170,7 @@ assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsLValue"); EvalInfo Info(Ctx, Result, EvalInfo::EM_ConstantFold); Info.InConstantContext = InConstantContext; LValue LV; @@ -15195,6 +15215,7 @@ assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsConstantExpr"); EvalInfo::EvaluationMode EM = EvalInfo::EM_ConstantExpression; EvalInfo Info(Ctx, Result, EM); Info.InConstantContext = true; @@ -15247,6 +15268,13 @@ assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + llvm::TimeTraceScope TimeScope("EvaluateAsInitializer", [&] { + std::string Name; + llvm::raw_string_ostream OS(Name); + VD->printQualifiedName(OS); + return Name; + }); + // FIXME: Evaluating initializers for large array and record types can cause // performance problems. Only do so in C++11 for now. if (isPRValue() && (getType()->isArrayType() || getType()->isRecordType()) && @@ -15335,6 +15363,7 @@ assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateKnownConstInt"); EvalResult EVResult; EVResult.Diag = Diag; EvalInfo Info(Ctx, EVResult, EvalInfo::EM_IgnoreSideEffects); @@ -15353,6 +15382,7 @@ assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateKnownConstIntCheckOverflow"); EvalResult EVResult; EVResult.Diag = Diag; EvalInfo Info(Ctx, EVResult, EvalInfo::EM_IgnoreSideEffects); @@ -15371,6 +15401,7 @@ assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateForOverflow"); bool IsConst; EvalResult EVResult; if (!FastEvaluateAsRValue(this, EVResult, Ctx, IsConst)) { @@ -15862,6 +15893,10 @@ assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + llvm::TimeTraceScope TimeScope("isIntegerConstantExpr", [&] { + return Loc->printToString(Ctx.getSourceManager()); + }); + if (Ctx.getLangOpts().CPlusPlus11) return EvaluateCPlusPlus11IntegralConstantExpr(Ctx, this, nullptr, Loc); @@ -15954,6 +15989,14 @@ assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); + llvm::TimeTraceScope TimeScope("EvaluateWithSubstitution", [&] { + std::string Name; + llvm::raw_string_ostream OS(Name); + Callee->getNameForDiagnostic(OS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + return Name; + }); + Expr::EvalStatus Status; EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantExpressionUnevaluated); Info.InConstantContext = true; @@ -16018,6 +16061,14 @@ if (FD->isDependentContext()) return true; + llvm::TimeTraceScope TimeScope("isPotentialConstantExpr", [&] { + std::string Name; + llvm::raw_string_ostream OS(Name); + FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), + /*Qualified=*/true); + return Name; + }); + Expr::EvalStatus Status; Status.Diag = &Diags; diff --git a/clang/unittests/CMakeLists.txt b/clang/unittests/CMakeLists.txt --- a/clang/unittests/CMakeLists.txt +++ b/clang/unittests/CMakeLists.txt @@ -47,3 +47,4 @@ add_subdirectory(Rename) add_subdirectory(Index) add_subdirectory(Serialization) +add_subdirectory(Support) diff --git a/clang/unittests/Support/CMakeLists.txt b/clang/unittests/Support/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/unittests/Support/CMakeLists.txt @@ -0,0 +1,12 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_unittest(ClangSupportTests + TimeProfilerTest.cpp + ) + +clang_target_link_libraries(ClangSupportTests + PRIVATE + clangFrontend + ) diff --git a/clang/unittests/Support/TimeProfilerTest.cpp b/clang/unittests/Support/TimeProfilerTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Support/TimeProfilerTest.cpp @@ -0,0 +1,198 @@ +//===- unittests/Support/TimeProfilerTest.cpp -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/PreprocessorOptions.h" + +#include "llvm/Support/JSON.h" +#include "llvm/Support/TimeProfiler.h" + +#include "gtest/gtest.h" + +using namespace clang; +using namespace llvm; + +namespace { + +// Should be called before testing. +void setupProfiler() { + timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, "test"); +} + +// Should be called after `compileFromString()`. +// Returns profiler's JSON dump. +std::string teardownProfiler() { + SmallVector SmallVec; + raw_svector_ostream OS(SmallVec); + timeTraceProfilerWrite(OS); + timeTraceProfilerCleanup(); + return OS.str().str(); +} + +// Returns true if code compiles successfully. +// We only parse AST here. This is enough for constexpr evaluation. +bool compileFromString(StringRef Code) { + CompilerInstance Compiler; + Compiler.createDiagnostics(); + + auto Invocation = std::make_shared(); + Invocation->getPreprocessorOpts().addRemappedFile( + "test.cc", MemoryBuffer::getMemBuffer(Code).release()); + const char *Args[] = {"-std=c++20", "test.cc"}; + CompilerInvocation::CreateFromArgs(*Invocation, Args, + Compiler.getDiagnostics()); + Compiler.setInvocation(std::move(Invocation)); + + class TestFrontendAction : public ASTFrontendAction { + private: + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override { + return std::make_unique(); + } + } Action; + return Compiler.ExecuteAction(Action); +} + +// Returns pretty-printed trace graph. +std::string buildTraceGraph(StringRef Json) { + struct EventRecord { + int64_t TimestampBegin; + int64_t TimestampEnd; + StringRef Name; + StringRef Detail; + }; + std::vector Events; + + // Parse `EventRecord`s from JSON dump. + Expected Root = json::parse(Json); + if (!Root) + return ""; + for (json::Value &TraceEventValue : + *Root->getAsObject()->getArray("traceEvents")) { + json::Object *TraceEventObj = TraceEventValue.getAsObject(); + + int64_t TimestampBegin = *TraceEventObj->getInteger("ts"); + int64_t TimestampEnd = TimestampBegin + *TraceEventObj->getInteger("dur"); + StringRef Name = *TraceEventObj->getString("name"); + StringRef Detail = ""; + if (json::Object *Args = TraceEventObj->getObject("args")) + Detail = Args->getString("detail").value_or(""); + + // This is a "summary" event, like "Total PerformPendingInstantiations", + // skip it + if (TimestampBegin == 0) + continue; + + Events.emplace_back( + EventRecord{TimestampBegin, TimestampEnd, Name, Detail}); + } + + // There can be nested events that are very fast, for example: + // {"name":"EvaluateAsBooleanCondition",... ,"ts":2380,"dur":1} + // {"name":"EvaluateAsRValue",... ,"ts":2380,"dur":1} + // Therefore we should reverse the events list, so that events that have + // started earlier are first in the list. + // Then do a stable sort, we need it for the trace graph. + std::reverse(Events.begin(), Events.end()); + std::stable_sort( + Events.begin(), Events.end(), [](const auto &lhs, const auto &rhs) { + return std::make_pair(lhs.TimestampBegin, lhs.TimestampEnd) < + std::make_pair(rhs.TimestampBegin, rhs.TimestampEnd); + }); + + std::stringstream Stream; + // Write a newline for better testing with multiline string literal. + Stream << "\n"; + + // Keep the current event stack. + std::stack EventStack; + for (const auto &Event : Events) { + // Pop every event in the stack until meeting the parent event. + while (!EventStack.empty()) { + bool InsideCurrentEvent = + Event.TimestampBegin >= EventStack.top()->TimestampBegin && + Event.TimestampEnd <= EventStack.top()->TimestampEnd; + if (!InsideCurrentEvent) + EventStack.pop(); + else + break; + } + EventStack.push(&Event); + + // Write indentaion, name, detail, newline. + for (size_t i = 1; i < EventStack.size(); ++i) { + Stream << "| "; + } + Stream.write(Event.Name.data(), Event.Name.size()); + if (!Event.Detail.empty()) { + Stream << " ("; + Stream.write(Event.Detail.data(), Event.Detail.size()); + Stream << ")"; + } + Stream << "\n"; + } + return Stream.str(); +} + +} // namespace + +TEST(TimeProfilerTest, ConstantEvaluation) { + constexpr StringRef Code = R"( +void print(double value); + +namespace slow_namespace { + +consteval double slow_func() { + double d = 0.0; + for (int i = 0; i < 100; ++i) { // 8th line + d += i; // 9th line + } + return d; +} + +} // namespace slow_namespace + +void slow_test() { + constexpr auto slow_value = slow_namespace::slow_func(); // 17th line + print(slow_namespace::slow_func()); // 18th line + print(slow_value); +} + +int slow_arr[12 + 34 * 56 + // 22nd line + static_cast(slow_namespace::slow_func())]; // 23rd line + +constexpr int slow_init_list[] = {1, 1, 2, 3, 5, 8, 13, 21}; // 25th line + )"; + + setupProfiler(); + ASSERT_TRUE(compileFromString(Code)); + std::string Json = teardownProfiler(); + std::string TraceGraph = buildTraceGraph(Json); + ASSERT_TRUE(TraceGraph == R"( +Frontend +| EvaluateAsRValue () +| EvaluateForOverflow () +| EvaluateAsRValue () +| EvaluateForOverflow () +| isPotentialConstantExpr (slow_namespace::slow_func) +| EvaluateAsBooleanCondition () +| | EvaluateAsRValue () +| EvaluateAsBooleanCondition () +| | EvaluateAsRValue () +| EvaluateAsInitializer (slow_value) +| EvaluateAsConstantExpr () +| EvaluateAsConstantExpr () +| EvaluateAsRValue () +| EvaluateAsInitializer (slow_init_list) +| PerformPendingInstantiations +)"); + + // NOTE: If this test is failing, run this test with + // `llvm::cerr() << TraceGraph;` and change the assert above. +}