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/

posted @ 2021-03-12 16:56  转换无极限  阅读(7622)  评论(0编辑  收藏  举报