[clang] Add time profile for constant evaluation

Add time profiler for various constexpr evaluation events
so that slow event could be visible on the visualized flame chart.

Reviewed By: aaron.ballman

Differential Revision: https://reviews.llvm.org/D136022
This commit is contained in:
Evgeny Shulgin 2022-10-15 17:11:40 +00:00
parent b430a352ef
commit 27d8eedd5a
5 changed files with 265 additions and 0 deletions

View File

@ -363,6 +363,8 @@ Non-comprehensive list of changes in this release
- Clang now supports ``__has_constexpr_builtin`` function-like macro that
evaluates to 1 if the builtin is supported and can be constant evaluated.
It can be used to writing conditionally constexpr code that uses builtins.
- The time profiler (using ``-ftime-trace`` option) now traces various constant
evaluation events.
New Compiler Flags
------------------

View File

@ -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 <cstring>
#include <functional>
@ -660,6 +661,19 @@ namespace {
CallStackFrame &Frame;
const LValue *OldThis;
};
// A shorthand time trace scope struct, prints source range, for example
// {"name":"EvaluateAsRValue","args":{"detail":"<test.cc:8:21, col:25>"}}}
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,
@ -15105,6 +15119,7 @@ bool Expr::EvaluateAsRValue(EvalResult &Result, const ASTContext &Ctx,
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);
@ -15114,6 +15129,7 @@ bool Expr::EvaluateAsBooleanCondition(bool &Result, const ASTContext &Ctx,
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);
@ -15124,6 +15140,7 @@ bool Expr::EvaluateAsInt(EvalResult &Result, const ASTContext &Ctx,
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);
@ -15134,6 +15151,7 @@ bool Expr::EvaluateAsFixedPoint(EvalResult &Result, const ASTContext &Ctx,
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);
@ -15148,6 +15166,7 @@ bool Expr::EvaluateAsFloat(APFloat &Result, const ASTContext &Ctx,
if (!getType()->isRealFloatingType())
return false;
ExprTimeTraceScope TimeScope(this, Ctx, "EvaluateAsFloat");
EvalResult ExprResult;
if (!EvaluateAsRValue(ExprResult, Ctx, InConstantContext) ||
!ExprResult.Val.isFloat() ||
@ -15163,6 +15182,7 @@ bool Expr::EvaluateAsLValue(EvalResult &Result, const ASTContext &Ctx,
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;
@ -15207,6 +15227,7 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx,
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;
@ -15259,6 +15280,13 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
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()) &&
@ -15347,6 +15375,7 @@ APSInt Expr::EvaluateKnownConstInt(const ASTContext &Ctx,
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);
@ -15365,6 +15394,7 @@ APSInt Expr::EvaluateKnownConstIntCheckOverflow(
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);
@ -15383,6 +15413,7 @@ void Expr::EvaluateForOverflow(const ASTContext &Ctx) const {
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)) {
@ -15874,6 +15905,10 @@ bool Expr::isIntegerConstantExpr(const ASTContext &Ctx,
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);
@ -15966,6 +16001,14 @@ bool Expr::EvaluateWithSubstitution(APValue &Value, ASTContext &Ctx,
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;
@ -16030,6 +16073,14 @@ bool Expr::isPotentialConstantExpr(const FunctionDecl *FD,
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;

View File

@ -47,3 +47,4 @@ add_subdirectory(DirectoryWatcher)
add_subdirectory(Rename)
add_subdirectory(Index)
add_subdirectory(Serialization)
add_subdirectory(Support)

View File

@ -0,0 +1,12 @@
set(LLVM_LINK_COMPONENTS
Support
)
add_clang_unittest(ClangSupportTests
TimeProfilerTest.cpp
)
clang_target_link_libraries(ClangSupportTests
PRIVATE
clangFrontend
)

View File

@ -0,0 +1,199 @@
//===- 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<char, 1024> 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<CompilerInvocation>();
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<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile) override {
return std::make_unique<ASTConsumer>();
}
} 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<EventRecord> Events;
// Parse `EventRecord`s from JSON dump.
Expected<json::Value> 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").value_or(0);
int64_t TimestampEnd =
TimestampBegin + TraceEventObj->getInteger("dur").value_or(0);
StringRef Name = TraceEventObj->getString("name").value_or("");
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<const EventRecord *> 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<int>(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 (<test.cc:8:21>)
| EvaluateForOverflow (<test.cc:8:21, col:25>)
| EvaluateAsRValue (<test.cc:9:14>)
| EvaluateForOverflow (<test.cc:9:9, col:14>)
| isPotentialConstantExpr (slow_namespace::slow_func)
| EvaluateAsBooleanCondition (<test.cc:8:21, col:25>)
| | EvaluateAsRValue (<test.cc:8:21, col:25>)
| EvaluateAsBooleanCondition (<test.cc:8:21, col:25>)
| | EvaluateAsRValue (<test.cc:8:21, col:25>)
| EvaluateAsInitializer (slow_value)
| EvaluateAsConstantExpr (<test.cc:17:33, col:59>)
| EvaluateAsConstantExpr (<test.cc:18:11, col:37>)
| EvaluateAsRValue (<test.cc:22:14, line:23:58>)
| EvaluateAsInitializer (slow_init_list)
| PerformPendingInstantiations
)");
// NOTE: If this test is failing, run this test with
// `llvm::errs() << TraceGraph;` and change the assert above.
}