zoukankan      html  css  js  c++  java
  • Clang教程之实现源源变化

    clang教程之实现源源变化

    声明:本教程来自于Eli Bendersky's website 

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

    众所周知,LLVM是一个自由软件项目,它是一种编译器基础设施,以C++写成。其发端源于2000年伊利诺伊大学厄巴纳-香槟分校(UIUC)的维克拉姆·艾夫(Vikram Adve)与其第一个博士生克里斯·拉特纳(Chris Lattner)的研究,彼时他们想要为所有静态及动态语言创造出动态的编译技术。现在使用LLVM来作为中端(middle-end)优化和后端目标代码生成的人很多,开源中也有很多基于LLVM进行二次开发的工具,比如之前NVIDIA贡献的nvptx的code-generator和klee。而llvm的前端,在llvm3(具体版本忘记了)之前使用的是GCC,之后使用的是clang。clang这个前端提供了很多sema静态分析工具,可以说已经超出了一般的编译器前端的范畴。

    clang的功能如此强大,但是却很少发现有人对这部分知识进行介绍。我这里选取了Eli Bendersky的博客进行翻译介绍,作者现在是Google TensorFlow组的工程师,中间添加了我自己的理解,如果有错误,望大家指出。

    首先介绍一下效果,输入是这样的一段带if的代码

    1 void foo(int* a, int *b) {
    2   if (a[0] > 1) {
    3     b[0] = 2;
    4   }
    5 }

    经过自己做的工具后,完成以下两部分的功能:

    1. 识别if的true-body和false-body,并添加相应的注释

    2. 识别函数入口和函数出口,添加注释

    介绍一下主要的实现思路:

    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
    ", true,
                                 true);
    
          Stmt *Else = IfStatement->getElse();
          if (Else)
            TheRewriter.InsertText(Else->getLocStart(), "// the 'else' part
    ",
                                   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
                   << "
    ";
          SourceLocation ST = f->getSourceRange().getBegin();
          TheRewriter.InsertText(ST, SSBefore.str(), true, true);
    
          // And after
          std::stringstream SSAfter;
          SSAfter << "
    // 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() << "
    ";
    
        // 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 << "
    ";
        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 -- 命令进行测试, --表示没有特别的参数

     

  • 相关阅读:
    【算法】二分图的判定
    【模板】并查集 两种路径压缩写法(类模板和函数模板)
    【模板】堆的结构
    新疆大学OJ(ACM) 1099: 数列有序!
    新疆大学OJ(ACM) 1047: string 字符串排序
    新疆大学(新大)OJ xju 1010: 四个年级 C++ STL map 将4层循环优化成2层循环可解
    新疆大学(新大)OJ xju 1009: 一带一路 prim求最短路径+O(n)素数筛选
    新疆大学(新大)OJ xju 1006: 比赛排名 第二类斯特林数+阶乘
    【算法】第二类斯特林数Stirling
    【复习资料】编译原理中:短语,直接短语,句柄
  • 原文地址:https://www.cnblogs.com/jourluohua/p/9526663.html
Copyright © 2011-2022 走看看