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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM