Clang教程之實現源源變化

clang教程之實現源源變化

聲明:本教程來自於Eli Bendersky's website  前端

原文地址:http://eli.thegreenplace.net/2014/05/01/modern-source-to-source-transformation-with-clang-and-libtooling/node

衆所周知,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());
}
View Code

源碼介紹到這裏,如今說一下編譯,這種項目編譯起來比較麻煩……

我選擇的環境是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 -- 命令進行測試, --表示沒有特別的參數

 

相關文章
相關標籤/搜索