• Clang AST介绍


    AST(Abstracted Syntax Tree)即抽象语法树,对于任何一门编程语言来说都是非常重要的工具,对于一般的compiler来说,都是将源码转换为AST,之后经由AST转换到特定的IR,在IR上进行一些与硬件特性无关的优化,之后再将优化后的IR转换为对应的汇编。因此AST直观的反应了使用者的编程思想。AST上一般进行的转换和优化不多,更多的是对语言特性的支持和检查。AST的中文介绍https://blog.csdn.net/philosophyatmath/article/details/38170131(入门介绍)

    对于AST是如何构建的,之前的词法token分析是如何进行的,在https://www.twilio.com/blog/abstract-syntax-trees中已经进行了很好的说明,不在进行赘述。一般而言,每种编程语言都有对应的ast,包括Python,Perl,Fortran,C,Java等,也有很多开源的AST Viewer工具可供使用,比如https://astexplorer.net/

    本文主要针对LLVM的前端进行介绍。在现有LLVM的版本中,默认使用的前端是Clang,Clang包含token和AST构建的全过程。(Clang官方的introduction:https://clang.llvm.org/docs/IntroductionToTheClangAST.html)

    AST树是源代码抽象语法结构,以树的形式表示语言的语法结构。树的每个节点都有相对应的源码的支持,树节点以不同的类型进行区分,下边将分别进行介绍。

    对于LLVM Clang来说,顶层结构是TranslationUnitDecl,对AST树的遍历,实际上是遍历整个TranslationUnitDecl。遍历它的方式,一般通过:

    DeclContext::decl_iterator D = Context.getTranslationUnitDecl()->decls_begin();
    DeclContext::decl_iterator DEnd = Context.getTranslationUnitDecl()->decls_end();
    while (D != DEnd)

    的方式进行。

    1. Clang的使用命令

    Clang尽最大可能兼容了gcc,因此将gcc修改为Clang,大概率只是换个名字就行,现在也有人尝试使用Clang编译一般的库,一般都能编过,而且代码运行效率一般不会特别差。有些系统下可能会面临std库无法找到的问题,需要额外添加-I和-L选项。

    Clang相比gcc,额外增加了很多功能,比如AST树的打印。对于源文件test.cpp:

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

    使用Clang -fsyntax-only -Xclang -ast-dump test.cpp

    简单解释下这里的命令,-fsyntax-only意味着只解析语法,不进行编译和链接;

                          -Xclang 是要使用clang特定的Xclang功能

                          -ast-dump是要打印AST

    这里会得到一个额外着色的ast,在源码中,使用dump是没有额外着色的,需要使用dumpColor()得到。

         2. AST树节点一般介绍(LangOptions()是CPP)

    我会以这样一段程序对AST树节点进行介绍,包含了函数声明、函数调用、变量声明、for循环、if语句、指针变量等。

     1 int foo(int a, int b,int *c){
     2     int ret = 0;
     3     if(a > b){
     4         ret = a;
     5     }
     6     else {
     7         ret = b;
     8     }
     9     for(int temp=0; temp<100; ++temp){
    10         *c = (*c + temp);
    11     }
    12     return ret;
    13 }
    14 
    15 int main(){
    16     int a = 1, b = 2;
    17     int c =0;
    18     int d = foo(a, b, &c);
    19     return 0;
    20 }

    FunctionDeclaration

    ParmVarDecl是参数节点

    在AST层级,不区分函数声明和函数定义,统一用FunctionDecl来标识,两个区分主要看是否有函数体(Body),可以使用bool hasBody()来进行判断。

    CompoundStmt

    代表大括号,函数实现、struct、enum、for的body等一般用此包起来。

    DeclStmt

    定义语句,里边可能有VarDecl等类型的定义

    VarDecl

    变量定义,如果有初始化,可以通过getInit()获取到对应的初始化Expr

    IfStmt

    If语句,包括三部分Cond、TrueBody、FalseBody三部分,分别可以通过getCond(),getThen(), getElse()三部分获取,Cond和Then是必须要有的,Else可能为空

    BinaryOperator

    二元操作Op,=,>,<,<=,>=,==等各种二元操作都继承它,从继承关系来说:

    通常通过getLHS()和getRHS()来分别获得其左右子节点

    ImplicitCastExpr

    隐形转换表达式,在左右值转换和函数调用等各个方面都会用到。

    IntegerLiteral

    定点Integer值

    UnaryOperator

    一元操作

    CallExpr

    函数调用Expr,子节点有调用的参数列表

    ReturnStmt

    返回语句

    ForStmt

    For语句对应,包括Init/Cond/Inc 对应(int a=0;a<mm;a++)这三部分,还有一部分是body,可以分别使用getInit() / getCond() / getInc() / getBody()来分别进行获取

    ParenExpr

    括号表达式

    对于一些结构体的操作,比如struct或者enum,有自己的格式。

    比如

    struct dict {
        int a;
        int b;
    };
    
    void vpp(){
        struct dict news;
        struct dict olds = {1,2};
        news.a = 1;
        news.b = b + 2;
    }

    RecordDecl首先是struct的声明,采用的是RecordDecl的形式。

    后边会出现对应的struct dict的类型

    在使用时会发现,仍然使用DeclStmt的形式去声明使用InitExpr的方式来初始化,只不过这种特殊的格式是使用了InitListExpr的方式。

    对于成员变量的使用,采用MemberExpr的方式来取值。

    Reference:

    https://clang.llvm.org/docs/index.html

    https://clang.llvm.org/docs/UsersManual.html

    https://www.twilio.com/blog/abstract-syntax-trees

    https://astexplorer.net/

  • 相关阅读:
    20道JS原理题助你面试一臂之力!
    10实用的JS实用技巧
    10实用的JS实用技巧
    比尔盖茨的30条经典语录
    比尔盖茨的30条经典语录
    成为优秀程序员应该具备的8个特质
    成为优秀程序员应该具备的8个特质
    不懂开发的人员,请不要随意说这功能很容易实现
    不懂开发的人员,请不要随意说这功能很容易实现
    15句乔布斯经典语录(中英文)
  • 原文地址:https://www.cnblogs.com/jourluohua/p/14524955.html
Copyright © 2020-2023  润新知