Analysing a file with AST matcher tool takes significant more time than just compile the same file

Hi,

I’m using clang AST matcher tool on MAC to analyse complex files with many many headers included. It turns out that it takes more than 10 times longer than just compile the same file with clang. Is it normal or am I doing something wrong? Thanks!

Best regards,
Han

Can you post the code for the matcher?

#include <stdio.h>
#include
#include
#include <system_error>
#include <type_traits>
#include “clang/AST/AST.h”
#include “clang/Frontend/TextDiagnosticPrinter.h”
#include “clang/Tooling/CommonOptionsParser.h”
#include “clang/ASTMatchers/ASTMatchers.h”
#include “clang/ASTMatchers/ASTMatchFinder.h”
#include “clang/Basic/SourceManager.h”
#include “clang/Frontend/FrontendActions.h”
#include “clang/Lex/Lexer.h”
#include “clang/Tooling/CompilationDatabase.h”
#include “clang/Tooling/Refactoring.h”
#include “clang/Tooling/Tooling.h”
#include “llvm/ADT/Twine.h”
#include “llvm/Support/CommandLine.h”
#include “llvm/Support/MemoryBuffer.h”
#include “llvm/Support/Path.h”
#include “llvm/Support/Signals.h”
#include “llvm/Support/raw_ostream.h”
#include “llvm/Support/TargetSelect.h”

using namespace llvm;
using clang::tooling::newFrontendActionFactory;
using clang::tooling::Replacement;
using clang::tooling::CompilationDatabase;
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::driver;
using namespace clang::tooling;

static llvm::cl::OptionCategory ToolingSampleCategory(“IsEmpty”);

// Returns the text that makes up ‘node’ in the source.
// Returns an empty string if the text cannot be found.
template
static std::string getText(const SourceManager &SourceManager, const T &Node) {
SourceLocation StartSpellingLocation =
SourceManager.getSpellingLoc(Node.getLocStart());
SourceLocation EndSpellingLocation =
SourceManager.getSpellingLoc(Node.getLocEnd());
if (!StartSpellingLocation.isValid() || !EndSpellingLocation.isValid()) {
return std::string();
}
bool Invalid = true;
const char *Text =
SourceManager.getCharacterData(StartSpellingLocation, &Invalid);
if (Invalid) {
return std::string();
}
std::pair<FileID, unsigned> Start =
SourceManager.getDecomposedLoc(StartSpellingLocation);
std::pair<FileID, unsigned> End =
SourceManager.getDecomposedLoc(Lexer::getLocForEndOfToken(
EndSpellingLocation, 0, SourceManager, LangOptions()));
if (Start.first != End.first) {
// Start and end are in different files.
return std::string();
}
if (End.second < Start.second) {
// Shuffling text with macros may cause this.
return std::string();
}
return std::string(Text, End.second - Start.second);
}

class ColIsEmptyHandler: public MatchFinder::MatchCallback {
public:
ColIsEmptyHandler(Replacements *Replace): Replace(Replace) {};
virtual void run(const MatchFinder::MatchResult &Result) {
if (const CXXMemberCallExpr callExpr = Result.Nodes.getNodeAsclang::CXXMemberCallExpr(“Col::IsEmptyExpr”)) {
const std::string text = getText(
(Result.SourceManager), *callExpr);

Replacement Rep(*(Result.SourceManager), CharSourceRange::getTokenRange(SourceRange(callExpr->getCallee()->getExprLoc())), “IsEmpty”);

Replace->insert(Rep);
}
}
private:
Replacements *Replace;
};

class ColIsEmptyInClassHandler: public MatchFinder::MatchCallback {
public:
ColIsEmptyInClassHandler(Replacements *Replace): Replace(Replace) {};
virtual void run(const MatchFinder::MatchResult &Result) {
if (const CXXMethodDecl decl = Result.Nodes.getNodeAsclang::CXXMethodDecl(“Col::IsEmptyInClass”)) {
const std::string text = getText(
(Result.SourceManager), *decl);

Replacement Rep(*(Result.SourceManager), CharSourceRange::getTokenRange(SourceRange(decl->getNameInfo().getLoc())), “IsEmpty”);
Replace->insert(Rep);
}
}
private:
Replacements *Replace;
};

int main(int argc, const char **argv) {
llvm::InitializeAllTargets();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmPrinters();
llvm::InitializeAllAsmParsers();
CommonOptionsParser op(argc, argv, ToolingSampleCategory);
RefactoringTool Tool(op.getCompilations(), op.getSourcePathList());
// Set up AST matcher callbacks.
ColIsEmptyHandler HandlerForColIsEmpty(&Tool.getReplacements());
ColIsEmptyInClassHandler HandlerForColIsEmptyInClass(&Tool.getReplacements());
MatchFinder Finder;
Finder.addMatcher(memberCallExpr(hasDeclaration(methodDecl(
hasName(“Empty”), ofClass(isSameOrDerivedFrom(hasName(“Col”)))))).bind(“Col::IsEmptyExpr”), &HandlerForColIsEmpty);
Finder.addMatcher(methodDecl(hasName(“Empty”), ofClass(isSameOrDerivedFrom(hasName(“Col”)))).bind(“Col::IsEmptyInClass”), &HandlerForColIsEmptyInClass);
// Run the tool and collect a list of replacements.
if (int Result = Tool.runAndSave(newFrontendActionFactory(&Finder).get())) {
return Result;
}
llvm::outs() << “Replacements collected by the tool:\n”;
for (auto &r : Tool.getReplacements()) {
llvm::outs() << r.toString() << “\n”;
}
return 0;
}

You’ll want to use Lexer::getSourceText instead of trying to write your own (trust me, it’s hard ;), but otherwise your code looks innocent enough. Are you compiling with at least -O2 and NDEBUG enabled?

Hi Manuel,

After using -O2 the speed is 30% faster from 10min to 7min. If I use clang to just compile this source file, it takes 40sec.

Best regards,
Han

We use truly a lot of templates in the files. Is it a possible reason why it’s so slow?

Shouldn’t be… you also don’t have overly unspecific matchers… I’d be curious about some profiling in your case…