LLVM 學習

LLVM 全稱是 Low Level Virtual Machine,開始時還不太理解 LLVM 到底有啥用,爲什麽這麽好用。

衆所周知 gcc 也可以劃分爲相應的 前端 中端 後端。爲什麽 gcc 沒有 llvm 怎麽好用,可見 llvm 的結構設計的比較優秀,非常適合代碼 reuse。把 AST 等 IR 做了隔離,讓我們可以非常方便的利用 IR 做一些工作。

接下來我打算從前端開始學習 LLVM 的源碼。 我認爲(書上寫的)還是先試一下這個 reuse 的特性。To be honest,之前并沒有學過 cmake,遇到問題了,基本也只是在源代碼的上面進行一點修改,並不需要掌握這個能力,趁現在我們也可以學習鞏固一番相關的知識。

今天不知道爲什麽效率有一點低,可能早上被 4 節思政課折磨的緣故,下午就看了會兒漫畫打了會鐵道,晚上再吃一個肯爺爺(鐵道周邊),穿插一些 b 站短視頻。有一絲絲罪惡感。

晚上稍微對 AST 進行了一點思考,LLVM 爲什麽要把諸如 “詞法分析” “語法分析” ”語義分析“ 進行一定的抽離,只是爲了代碼復用嗎,既然要代碼復用,他的目的是什麽呢,如果沒有代碼復用的必要,必然沒有拆離的必要。這也許可以部分解釋之前 GCC 爲什麽沒有這樣設計。也就是説,詞法分析後的 Token,語法分析樹、三地址代碼、抽象語法樹等中間表示(IR)是有各自的特點(優點/缺點的),如何結合各自的優缺點進行工作,也許是非常值得研究的方向。

爲了熟悉 AST,我們先看一下 llvm 的 AST 是怎麽樣的

\\ hello.c
#include <stdio.h>

int main(){
    int i = 0, j = 3, k = i+j;
    printf("hello wolrd %d\n", k);
    return 0;
}

clang -target riscv64 -march=rv64g -Xclang -ast-dump -fsyntax-only hello.c

其中 -Xclang 表示把後面的參數傳給 clang 前端,-ast-dump 打印出抽象語法樹。

......
|-TypedefDecl 0x7527a8 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'void *'
| `-PointerType 0x71aa10 'void *'
|   `-BuiltinType 0x719ab0 'void'
|-FunctionDecl 0x777bc8 <hello.c:3:1, line:7:1> line:3:5 main 'int ()'
| `-CompoundStmt 0x7782e8 <col:11, line:7:1>
|   |-DeclStmt 0x777f20 <line:4:5, col:30>
|   | |-VarDecl 0x777cc8 <col:5, col:13> col:9 used i 'int' cinit
|   | | `-IntegerLiteral 0x777d30 <col:13> 'int' 0
|   | |-VarDecl 0x777d68 <col:5, col:20> col:16 used j 'int' cinit
|   | | `-IntegerLiteral 0x777dd0 <col:20> 'int' 3
|   | `-VarDecl 0x777e08 <col:5, col:29> col:23 used k 'int' cinit
|   |   `-BinaryOperator 0x777ee0 <col:27, col:29> 'int' '+'
|   |     |-ImplicitCastExpr 0x777eb0 <col:27> 'int' <LValueToRValue>
|   |     | `-DeclRefExpr 0x777e70 <col:27> 'int' lvalue Var 0x777cc8 'i' 'int'
|   |     `-ImplicitCastExpr 0x777ec8 <col:29> 'int' <LValueToRValue>
|   |       `-DeclRefExpr 0x777e90 <col:29> 'int' lvalue Var 0x777d68 'j' 'int'
|   |-CallExpr 0x778240 <line:5:5, col:33> 'int'
|   | |-ImplicitCastExpr 0x778228 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
|   | | `-DeclRefExpr 0x778118 <col:5> 'int (const char *, ...)' Function 0x777f78 'printf' 'int (const char *, ...)'
|   | |-ImplicitCastExpr 0x778288 <col:12> 'const char *' <NoOp>
|   | | `-ImplicitCastExpr 0x778270 <col:12> 'char *' <ArrayToPointerDecay>
|   | |   `-StringLiteral 0x778178 <col:12> 'char[16]' lvalue "hello wolrd %d\n"
|   | `-ImplicitCastExpr 0x7782a0 <col:32> 'int' <LValueToRValue>
|   |   `-DeclRefExpr 0x7781a0 <col:32> 'int' lvalue Var 0x777e08 'k' 'int'
|   `-ReturnStmt 0x7782d8 <line:6:5, col:12>
|     `-IntegerLiteral 0x7782b8 <col:12> 'int' 0
`-FunctionDecl 0x777f78 <line:5:5> col:5 implicit used printf 'int (const char *, ...)' extern
  |-ParmVarDecl 0x778070 <<invalid sloc>> <invalid sloc> 'const char *'
  |-BuiltinAttr 0x778018 <<invalid sloc>> Implicit 824
  `-FormatAttr 0x7780e0 <col:5> Implicit printf 1 2

我們很清楚的知道 1+2 的 AST 描述,但是如果只考慮 1+2,顯然無法表示整個程序。雖然在構建 AST 的過程中會丟失信息,但是至少還是要能表示程序的。如何構建 AST,AST 有什麽樣的作用,僅僅是一個中間環節嗎,還需要更深的理解。