聲明:本教程來自於Eli Bendersky's website 前端
衆所周知,LLVM是一個自由軟件項目,它是一種編譯器基礎設施,以C++寫成。其發端源於2000年伊利諾伊大學厄巴納-香檳分校(UIUC)的維克拉姆·艾夫(Vikram Adve)與其第一個博士生克里斯·拉特納(Chris Lattner)的研究,彼時他們想要爲全部靜態及動態語言創造出動態的編譯技術。如今使用LLVM來做爲中端(middle-end)優化和後端目標代碼生成的人不少,開源中也有不少基於LLVM進行二次開發的工具,好比以前NVIDIA貢獻的nvptx的code-generator和klee。而llvm的前端,在llvm3(具體版本忘記了)以前使用的是GCC,以後使用的是clang。clang這個前端提供了不少sema靜態分析工具,能夠說已經超出了通常的編譯器前端的範疇。web
clang的功能如此強大,可是卻不多發現有人對這部分知識進行介紹。我這裏選取了Eli Bendersky的博客進行翻譯介紹,做者如今是Google TensorFlow組的工程師,中間添加了我本身的理解,若是有錯誤,望你們指出。後端
首先介紹一下效果,輸入是這樣的一段帶if的代碼dom
1 void foo(int* a, int *b) { 2 if (a[0] > 1) { 3 b[0] = 2; 4 } 5 }
通過本身作的工具後,完成如下兩部分的功能:ide
1. 識別if的true-body和false-body,並添加相應的註釋函數
2. 識別函數入口和函數出口,添加註釋工具
介紹一下主要的實現思路:oop
1. 建立ClangTool,也就是使用libTooling的方式,解析輸入的參數,將第1個參數做爲源碼文件進行讀取測試
2. 將源碼送到ASTConsumer中,進行解析
3. ASTConsumer中,重載HandleTopLevelDecl識別全部的函數聲明
4. 調用MyASTVisitor這個類(繼承至TheWriter)中的VisitStmt函數,對全部的語句進行遍歷,調用VisitFunctionDecl函數,對函數聲明進行處理
5. 在遍歷中識別是不是IfStmt,而後對ture-body和false-body進行識別,並添加註釋
6. 將修改完的送回TheRewriter,進行寫回
如今粘一下源碼LoopConvert.cpp
//------------------------------------------------------------------------------ // Tooling sample. Demonstrates: // // * How to write a simple source tool using libTooling. // * How to use RecursiveASTVisitor to find interesting AST nodes. // * How to use the Rewriter API to rewrite the source code. // // Eli Bendersky (eliben@gmail.com) // This code is in the public domain //------------------------------------------------------------------------------ #include <sstream> #include <string> #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/ASTConsumers.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/raw_ostream.h" using namespace clang; using namespace clang::driver; using namespace clang::tooling; static llvm::cl::OptionCategory ToolingSampleCategory("Tooling Sample"); // By implementing RecursiveASTVisitor, we can specify which AST nodes // we're interested in by overriding relevant methods. class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> { public: MyASTVisitor(Rewriter &R) : TheRewriter(R) {} bool VisitStmt(Stmt *s) { // Only care about If statements. if (isa<IfStmt>(s)) { IfStmt *IfStatement = cast<IfStmt>(s); Stmt *Then = IfStatement->getThen(); TheRewriter.InsertText(Then->getLocStart(), "// the 'if' part\n", true, true); Stmt *Else = IfStatement->getElse(); if (Else) TheRewriter.InsertText(Else->getLocStart(), "// the 'else' part\n", true, true); } return true; } bool VisitFunctionDecl(FunctionDecl *f) { // Only function definitions (with bodies), not declarations. if (f->hasBody()) { Stmt *FuncBody = f->getBody(); // Type name as string QualType QT = f->getReturnType(); std::string TypeStr = QT.getAsString(); // Function name DeclarationName DeclName = f->getNameInfo().getName(); std::string FuncName = DeclName.getAsString(); // Add comment before std::stringstream SSBefore; SSBefore << "// Begin function " << FuncName << " returning " << TypeStr << "\n"; SourceLocation ST = f->getSourceRange().getBegin(); TheRewriter.InsertText(ST, SSBefore.str(), true, true); // And after std::stringstream SSAfter; SSAfter << "\n// End function " << FuncName; ST = FuncBody->getLocEnd().getLocWithOffset(1); TheRewriter.InsertText(ST, SSAfter.str(), true, true); } return true; } private: Rewriter &TheRewriter; }; // Implementation of the ASTConsumer interface for reading an AST produced // by the Clang parser. class MyASTConsumer : public ASTConsumer { public: MyASTConsumer(Rewriter &R) : Visitor(R) {} // Override the method that gets called for each parsed top-level // declaration. bool HandleTopLevelDecl(DeclGroupRef DR) override { for (DeclGroupRef::iterator b = DR.begin(), e = DR.end(); b != e; ++b) { // Traverse the declaration using our AST visitor. Visitor.TraverseDecl(*b); (*b)->dump(); } return true; } private: MyASTVisitor Visitor; }; // For each source file provided to the tool, a new FrontendAction is created. class MyFrontendAction : public ASTFrontendAction { public: MyFrontendAction() {} void EndSourceFileAction() override { SourceManager &SM = TheRewriter.getSourceMgr(); llvm::errs() << "** EndSourceFileAction for: " << SM.getFileEntryForID(SM.getMainFileID())->getName() << "\n"; // Now emit the rewritten buffer. TheRewriter.getEditBuffer(SM.getMainFileID()).write(llvm::outs()); } std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef file) override { llvm::errs() << "** Creating AST consumer for: " << file << "\n"; TheRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts()); return llvm::make_unique<MyASTConsumer>(TheRewriter); } private: Rewriter TheRewriter; }; int main(int argc, const char **argv) { CommonOptionsParser op(argc, argv, ToolingSampleCategory); ClangTool Tool(op.getCompilations(), op.getSourcePathList()); // ClangTool::run accepts a FrontendActionFactory, which is then used to // create new objects implementing the FrontendAction interface. Here we use // the helper newFrontendActionFactory to create a default factory that will // return a new MyFrontendAction object every time. // To further customize this, we could create our own factory class. return Tool.run(newFrontendActionFactory<MyFrontendAction>().get()); }
源碼介紹到這裏,如今說一下編譯,這種項目編譯起來比較麻煩……
我選擇的環境是Ubuntu16.04+LLVM4.0+Clang4.0 的環境,我已經發過一個使用binary進行安裝llvm教程,固然,那個教程不適用於這裏,我改天會再發一個教程,如何使用源碼進行編譯
這裏假設你們和我使用的是相同的環境,由於LLVM4.0到5.0經歷了比較大的改動,4.0的代碼在5.0上正常編譯時很是正常的。
1. 在源碼的clang/tools文件夾下(大概是~/llvm-src/llvm-4.0.0.src/tools/clang/tools/下),新建文件夾extra
在文件夾內新建CMakeLists.txt,寫入
add_subdirectory(loop-convert)
這裏是告訴cmake工具,下邊還有一級目錄,叫作loop-convert
2. 再在extra中新建loop-convert文件夾
3. 在loop-convert中新建CMakeLists.txt,寫入
set(LLVM_LINK_COMPONENTS support) add_clang_executable(loop-convert LoopConvert.cpp ) target_link_libraries(loop-convert clangTooling clangBasic clangASTMatchers )
大概意思是添加LLVM的支持,使用 LoopConvert.cpp來編譯出一個叫loop-convert的程序,而後將loop-convert和 clangTooling clangBasic clangASTMatchers連接在一塊兒,這幾個都是clang的庫
如今loop-convert文件夾中應該有CMakeLists.txt LoopConvert.cpp兩個文件
如今的目錄結構以下
clang/tools ->extra -> loop-convert ->CMakeLists.txt
... CMakeLists.txt LoopConvert.cpp
4. 如今,從新使用cmake生成Makefile文件,make後就能獲得loop-convert了
loop-convert在 where_you_build/bin/下邊(我是~/llvm-src/build/bin)
如今進行測試
首先編輯一個帶if的程序(不推薦包含頭文件,由於AST打印的時候,會把頭文件也打印出來,不方便查看)
我使用的test.cpp以下
void foo(int* a, int *b) { if (a[0] > 1) { b[0] = 2; } }
使用./loop-convert test.cpp -- 命令進行測試, --表示沒有特別的參數