lab3實驗報告,我的實驗報告圖例很少,這次只有兩張圖,其余的都以復制輸出的形式展現出來了,最終提交的代碼在最后
- [[#你的提交|你的提交]]
- [[#實驗設計|實驗設計]]
- [[#提交一:手動編寫.ll|提交一:手動編寫.ll]]
- [[#提交一:手動編寫.ll#assing(20)|assing(20)]]
- [[#提交一:手動編寫.ll#fun(220)|fun(220)]]
- [[#提交一:手動編寫.ll#if(233)|if(233)]]
- [[#提交一:手動編寫.ll#while(65)|while(65)]]
- [[#提交2:利用LightIR+cpp編寫生成.ll程序|提交2:利用LightIR+cpp編寫生成.ll程序]]
- [[#提交2:利用LightIR+cpp編寫生成.ll程序#結果驗證|結果驗證]]
- [[#問題1:cpp與.ll的對應|問題1:cpp與.ll的對應]]
- [[#問題2:Vistor Pattern|問題2:Vistor Pattern]]
- [[#問題3: getelementptr|問題3: getelementptr]]
- [[#實驗難點|實驗難點]]
- [[#實驗反饋|實驗反饋]]
- [[#附錄:編寫文件|附錄:編寫文件]]
- [[#附錄:編寫文件#assign_hand.ll|assign_hand.ll]]
- [[#附錄:編寫文件#full_hand.ll|full_hand.ll]]
- [[#附錄:編寫文件#if_hand.ll|if_hand.ll]]
- [[#附錄:編寫文件#while_hand.ll|while_hand.ll]]
- [[#附錄:編寫文件#assign的cpp文件|assign的cpp文件]]
- [[#附錄:編寫文件#full的cpp文件|full的cpp文件]]
- [[#附錄:編寫文件#if的cpp文件|if的cpp文件]]
- [[#附錄:編寫文件#while的cpp文件|while的cpp文件]]
你的提交
助教提供了四個簡單的c程序,分別是tests/lab3/c_cases/目錄下的assign.c、fun.c、if.c、while.c.
你需要在tests/lab3/stu_ll/目錄中,手工完成自己的assign_hand.ll、fun_hand.ll、if_handf.ll和while_hand.ll,以實現與上述四個C程序相同的邏輯功能.你需要添加必要的注釋.ll文件的注釋是以";"開頭的。
必要的情況下,你可以參考clang -S -emit-llvm的輸出,但是你提交的結果必須避免同此輸出一字不差。
助教會用lli檢查你結果的正確性,并用肉眼檢查你的注釋。
- 需要完成
./tests/lab3/stu_ll
目錄下的4個文件 - 需要完成
./tests/lab3/stu_cpp
目錄下的4個文件 - 需要在
./Report/lab3/
目錄下撰寫實驗報告
實驗設計
1、用lli --version檢查LLVM版本
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ lli --version
LLVM (http://llvm.org/):LLVM version 10.0.0Optimized build.Default target: x86_64-pc-linux-gnuHost CPU: skylake
10.0.0與10.0.1差別比較小,問題不大
2.利用clang -S -emit-llvm gcd_array.c
和lli gcd_array.ll; echo $?
gcc -o gcd_array gcd_array.c
和./gcd_array;echo $?
輸出:
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/ta_gcd$ clang -S -emit-llvm gcd_array.c
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/ta_gcd$ lli gcd_array.ll; echo $?
18
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/ta_gcd$ gcc -o gcd_array gcd_array.c
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/ta_gcd$ ./gcd_array;echo $?
18
可以看到,輸出結果均為18。因此,可以驗證 gcd_array.ll 文件正確地對應了 gcd_array.c 文件。
3.回到主目錄中的build文件夾,使用命令:
編譯與運行在 `${WORKSPACE}/build/` 下執行:
# 如果存在 CMakeCache.txt 要先刪除
# rm CMakeCache.txt
cmake ..
make
make install
你可以得到對應`gcd_array_generator.cpp`的可執行文件。
我們可以看到gcd_array_generator文件
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ ls
CMakeCache.txt gcd_array_generator libflex.a Makefile test_logging
CMakeFiles lexer libIR_lib.a parser tests
cmake_install.cmake libcminus_io.a libOP_lib.a src
cminusfc libcommon.a libsyntax.a test_ast
再使用./gcd_array_generator
,輸出的便是gcd_array_generator.ll
提交一:手動編寫.ll
assing(20)
在tests/lab3/c_cases中查看assign.c文件
int main(){int a[10];a[0] = 10;a[1] = a[0] * 2;return a[1];
}
寫出對應的assign_hand.ll文件,在附錄中
驗證輸入
lli assign_hand.ll; echo $?gcc -o assign assign.c./assign; echo $?
輸出
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/c_cases$ gcc -o assign assign.c
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/c_cases$ ./assign; echo $?
20
和:
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/stu_ll$ lli assign_hand.ll; echo $?
20
fun(220)
fun.c文件
int callee(int a){return 2 * a;
}
int main(){return callee(110);
}
寫出對應的full_hand.h,在附錄中
驗證輸入:
lli fun_hand.ll; echo $?gcc -o fun fun.c./fun; echo $?
輸出:
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/c_cases$ gcc -o fun fun.c
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/c_cases$ ./fun; echo $?
220
和:
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/stu_ll$ lli fun_hand.ll; echo $?
220
if(233)
查看if.c文件
int main(){float a = 5.555;if(a > 1)return 233;return 0;
}
寫出對應的if_hand.ll,在附錄中
驗證輸入
lli if_hand.ll; echo $?gcc -o if if.c./if; echo $?
輸出
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/c_cases$ gcc -o if if.c
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/c_cases$ ./if; echo $?
233
和
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/stu_ll$ lli if_hand.ll; echo $?
233
while(65)
查看while.c文件
int main(){int a;int i;a = 10;i = 0;while(i < 10){i = i + 1;a = a + i;}return a;
}
寫出對應while_hand.ll文件,在附錄中
驗證輸出
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/stu_ll$ lli while_hand.ll; echo $?
65
和
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/c_cases$ gcc -o while while.c
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/tests/lab3/c_cases$ ./while; echo $?
65
提交2:利用LightIR+cpp編寫生成.ll程序
根據gcd_array的例子來寫。注意后續測試的時候需要在${WORKSPACE}/tests/lab3/CMakeLists.txt
中去掉對應的注釋,再在${WORKSPACE}/build/
下執行cmake ..
與make
指令,即可得到對應的可執行文件。
編寫四個.cpp文件,然后生成對應的可執行文件
![[Snipaste_2023-12-08_17-35-29.png]]
結果驗證
assign
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ ./stu_assign_generator > assign.ll
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ lli assign.ll echo $?
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ lli assign.ll; echo $?
20
fill
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ ./stu_fun_generator > fun.ll
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ lli fun.ll;echo $?
220
if
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ ./stu_if_generator >if.ll
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ lli if.ll;echo $?
233
while
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ ./stu_while_generator > while.ll
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ lli while.ll;echo $?
65
問題1:cpp與.ll的對應
概述:請描述你的cpp代碼片段和.ll的每個BasicBlock的對應關系。描述中請附上兩者代碼。
用命令./stu_assign_generator > assign.ll
生成assign.ll文件中只有一個BasicBlock,在main函數中,名為entry,代碼如下:
define i32 @main() {
label_entry:%op0 = alloca i32store i32 0, i32* %op0%op1 = alloca [10 x i32]%op2 = getelementptr [10 x i32], [10 x i32]* %op1, i32 0, i32 0store i32 10, i32* %op2%op3 = load i32, i32* %op2%op4 = mul i32 %op3, 2%op5 = getelementptr [10 x i32], [10 x i32]* %op1, i32 0, i32 1store i32 %op4, i32* %op5%op6 = load i32, i32* %op5ret i32 %op6
}
對應的cpp
代碼auto bb = BasicBlock::create(module, “entry”, mainFun);創建名為entry的基本塊,并將其存到變量bb中(在打印輸出時,名字實際為laebl_entry)
代碼builder->set_insert_point(bb);用于進行SetInsertPoint設置,即將bb加入builder中。
而之后的代碼,則是具體的基本塊中的所需執行的語句。
// main函數->對應label_entryauto mainFun = Function::create(FunctionType::get(Int32Type, {}), /* 創建 main 函數 */"main", module);auto bb = BasicBlock::create(module, "entry", mainFun); /* 創建基本塊 */// BasicBlock的名字在生成中無所謂,但是可以方便閱讀builder->set_insert_point(bb); /* 將基本塊加入到builder中 */auto retAlloca = builder->create_alloca(Int32Type); /* 創建返回默認量 */builder->create_store(CONST_INT(0), retAlloca); /* 給默認量賦0,表示默認ret 0 */auto *arrayType = ArrayType::get(Int32Type, 10); /* 申請10個int的內存空間,將地址指針存入arrayType中 */auto aAlloca = builder->create_alloca(arrayType); /* 創建aAlloca數組,即a[10] */auto a0 = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(0)}); /* 用變量a0存指向a[0]的指針 */builder->create_store(CONST_INT(10), a0); /* 將10存入a[0] */auto tmp = builder->create_load(a0); /* 取出a[0]的值存入變量tmp */auto mul = builder->create_imul(tmp, CONST_INT(2)); /* 將值乘以2存入變量mul中 */auto a1 = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(1)}); /* 用變量a1存指向a[1]的指針 */builder->create_store(mul, a1); /* 將結果mul存入a[1]中 */auto res = builder->create_load(a1); /* 取出a[1]中的值作為返回結果,存到變量res中 */builder->create_ret(res); /* 創建返回,將res返回 */std::cout << module->print();delete module;
而在基本塊之間的跳轉時,用代碼builder->create_br(whileBB);進行直接跳轉到名為whileBB的基本塊;
用代碼builder->create_cond_br(icmp, trueBB, falseBB);進行按條件icmp跳轉到trueBB基本塊或是falseBB基本塊。
問題2:Vistor Pattern
請指出visitor.cpp中,treeVisitor.visit(exprRoot)執行時,以下幾個Node的遍歷序列:numberA、numberB、exprC、exprD、exprE、numberF、exprRoot。
序列請按如下格式指明:
exprRoot->numberF->exprE->numberA->exprD
答:根據main函數,我們可以得出如下步驟:
1.創建了值為4的A節點和值為2的B節點
auto numberA = NumberNode(4);
auto numberB = NumberNode(2);
2.A作為左節點,B作為右節點,相乘得到exprC;B作為左節點,A作為右節點相除得到exprD
auto exprC = MulDivNode(numberA, numberB, "mul");
auto exprD = MulDivNode(numberB, numberA, "div");
3.exprC作為左節點,exprD作為右節點,相減得到exprE
auto exprE = AddSubNode(exprC, exprD, "sub");
4.創建值為4的F節點
auto numberF = NumberNode(5);
5.exprE作為左節點,F作為右節點,相加得到exprRoot
auto exprRoot = AddSubNode(exprE, numberF, "add");
綜上,可以構建出如下的一棵樹:
節點訪問順序如圖所示,文字描述如下:
- 訪問exprRoot時,由于為AddSubNode,先訪問右子節點numberF,返回一個值,再訪問左子節 點exprE;
- 訪問exprE時,由于為AddSubNode,先訪問右子節點exprD,再訪問左子節點exprC;
- 訪問exprD時,由于為MulDivNode,先訪問左節點numberB,返回一個值,再訪問右節點 numberA,返回一個值;
- 訪問exprC時,由于為MulDivNode,先訪問左節點numberA,返回一個值,再訪問右節點 numberB,返回一個值; 總結:訪問順序為exprRoot->numberF->exprE->exprD->numberB->numberA->exprC- >numberA->numberB
問題3: getelementptr
請給出IR.md中提到的兩種getelementptr用法的區別,并稍加解釋:
%2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0
%2 = getelementptr i32, i32* %1 i32 %0
答:GetElementPtr指令用于獲取聚合數據結構的子元素的地址。它僅執行地址計算,不訪問內存。是一條指針計算語句,本身并不進行任何數據的訪問或修改,只進行指針的計算。
從結果的角度講,兩種getelementptr的用法的結果都是將一個i32*
類型的,即一個i32
內存的地址存入%2
中。
第一種方法:數組在C中會分割指針,但在LLVM中,他只能確定數據類型的大小然后強制轉換為指針,但不會分割他們。%1是基址,有兩個索引0和%0,數組通過指針訪問,所以需要兩個索引
第一個用于分割指針(因為用的指針是[10 x i32]
,但是我們返回的是一個i32的,所以需要分割),
第二個用于索引數組本身(即偏移量)。
該方法適用的數組為int nums[] = {1, 2, 3};
或者int a[10]
.
第二種方法:%1做為我們的基址地址,%0做為我們的索引(偏移量),并把計算出來的地址給%2。該方法適用的數組為int *nums = {1, 2, 3};
實際的用法中有著如下的區別:
1. 兩種用法對于偏移值參數數量要求不同
對于 %2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0 ,將內存按照10*i32 的大小,即40字節,進行劃分,而每個10*i32大小的內存中又按照i32,即4字節,進行劃分,分為10個i32大小。此時需要兩個偏移類型及偏移值來獲得所需地址。可以將整個內存看做一個二維數組,每一個 元素代表一個i32大小內存,每一行為10個i32。其中第一個偏移值代表行號,以題目中i320為例,首先地址應當偏移0*40=0個字節;第二個偏移值代表列號,以題目中i32 %0為例,地址應再偏移%0*4=0個字節。最終獲得的地址偏移量為0*40+0%*40=0%*40。(偏移是針對所給索引開始的指針%1的) 對于 %2 = getelementptr i32, i32* %1, i32 %0 將內存按照i32的大小,即4字節進行劃分。此時需要一個偏移類型及偏移值來獲取所需地址。可以將整個內存看做一個一維數組,每一個元素代表一個i32大小內存。其中偏移值代表元素的序號,以題目中i32 %0為例,地址應當偏移%0*4字節,即取第 [%0]個i32所在的地址。 2. 兩種用法對于索引開始的指針類型和計算基礎類型要求不同
對于 %2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0 ,要求傳入的%1必須 為[10 x i32]* 類型;而對于 %2 = getelementptr i32, i32* %1, i32 %0 ,要求傳入的%1必須為 i32*類型。 3. 兩種用法的使用場景不同
對于 %2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0 ,由于要求傳入的%1 必須為[10 x i32]* 類型,則對于一個使用了上述方法的函數,該方法所針對的數組必須為一個在該函數 中長度特定且已知的數組,例如在本函數中申請的a[10]或者全局數組b[5]等,而函數形參中的u[]所傳遞 的數組由于長度未知,不能使用該方法。該方法兼容性較差,只能針對特定數組。對于 %2 = getelementptr i32, i32* %1, i32 %0 ,由于要求傳入的%1必須為i32*類型,則對于一個使用了該方法的函數,該數組為一個在該函數中長度不確定的數組,例如:該數組為在其他函數中 申請的數組,然后作為本函數的形參例如u[](不確定長數組)的實參傳入本函數。一個函數為了盡可能處理不同長度的數組,故使用該方法,因為兼容性較好,能夠針對不同長度的數組 二者的使用區別有點類似于C語言中對于靜態數組和動態數組的處理,一個是長度固定,一個是長度可變。
實驗難點
第一部分是根據C程序完成.ll文件,這一部分的難點主要在于對IR的理解,要讀懂手冊。因為有匯編的基礎以及查找到的北航編譯原理實驗的資料、助教提供的詳盡的代碼注釋,理解起來并不算 難,在getelementptr這塊費了比較大勁,但靠不斷查閱資料自我消化還是理解了用法。
最難的就是補充.cpp文件然后生成.ll再驗證
對變量進行操作時,要先給變量分配位置,用store將值賦給這個位置,再把它load出來
寫分支時要理清楚進入分支、分支、出分支幾部分
實驗反饋
通俗易懂,又不算特別懂,快速上手,又有一些難度,代碼和注釋都很詳細,nice!
附錄:編寫文件
assign_hand.ll
在tests/lab3/c_cases中查看assign.c文件
int main(){int a[10];a[0] = 10;a[1] = a[0] * 2;return a[1];
}
;int main(){
define dso_local i32 @main() #0 {;int a[10]
%1 = alloca [10 x i32] ;%1為a[10]的起始地址;a[0] = 10;
%2 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i64 0, i64 0 ;%2為a[0]
store i32 10, i32* %2 ;%2賦值為10,完成a[0]賦值;a[1] = a[0] * 2;
%3 = load i32, i32* %2 ;%3加載%2的數據
%4 = mul nsw i32 %3, 2 ;%4賦值為%3的二倍
%5 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i64 0, i64 1 ;%5存儲a[1]
store i32 %4, i32* %5 ;把%4的值賦值給%5,完成a[1]賦值;return a[1];
%6 = load i32, i32* %5 ;%6存儲%5
ret i32 %6 ;返回%6;}
}
full_hand.ll
fun.c文件
int callee(int a){return 2 * a;
}
int main(){return callee(110);
}
;int callee(int a){
define dso_local i32 @callee(i32 %0) #0 {
%2 = alloca i32 ;開辟空間 為什么不能%1???
store i32 %0, i32* %2 ;%2存儲參數a
%3 = load i32, i32* %2 ;%3保存%2;return 2 * a;
%4 = mul nsw i32 2, %3 ;%4存儲二倍的%3
ret i32 %4 ;返回%4;}
};int main(){
define dso_local i32 @main() #0 {;return callee(110);
%1 = call i32 @callee(i32 110) ;%1存儲callee()返回值 為什么不能%0???
ret i32 %1 ;返回%1;}
}
if_hand.ll
查看if.c文件
int main(){float a = 5.555;if(a > 1)return 233;return 0;
}
;int main(){
define dso_local i32 @main() #0 {;float a = 5.555;
%1 = alloca i32 ;開辟空間
%2 = alloca float ;開辟float空間
store float 0x40163851E0000000, float* %2 ;%2賦值為5.555;if(a > 1)
%3 = load float, float* %2 ;把%2的數據加載到%3
%4 = fcmp ogt float %3, 1.000000e+00 ;%3和1比較
br i1 %4, label %5, label %6 ;4是則跳轉到5,4否則跳轉到6;return 233;
5:
store i32 233, i32* %1 ;%1賦值為233
br label %7 ;跳轉到7;return 0;
6:
store i32 0, i32* %1 ;%1賦值為0
br label %7 ;跳轉到77:
%8 = load i32, i32* %1 ;把%1的數據加載到%8
ret i32 %8 ;返回%8;}
}
while_hand.ll
查看while.c文件
int main(){int a;int i;a = 10;i = 0;while(i < 10){i = i + 1;a = a + i;}return a;
}
;int main(){
define dso_local i32 @main() #0 {;int a;
%1 = alloca i32 ;開辟空間;int i;
%2 = alloca i32 ;開辟空間;a = 10;
store i32 10, i32* %1 ;%1賦值為10;i = 0;
store i32 0, i32* %2 ;%2賦值為0
br label %3 ;跳轉到3;while(i < 10){
3:
%4 = load i32, i32* %2 ;把%2的值加載到%4
%5 = icmp slt i32 %4, 10 ;比較%4和10的大小
br i1 %5, label %6, label %11 ;5是則跳轉到6,5否則跳轉到11;i = i + 1;
6:
%7 = add nsw i32 %4, 1 ;%7保存加一后的值
store i32 %7, i32* %2 ;存儲回%2;a = a + i;
%8 = load i32, i32* %1 ;a的數據加載到%8
%9 = load i32, i32* %2 ;i的數據加載到%9
%10 = add nsw i32 %8, %9 ;%10保存相加的數據
store i32 %10, i32* %1 ;存儲回%1
br label %3 ;跳轉到3;};return a;
11:
%12 = load i32, i32* %1 ;a的數據加載到%12
ret i32 %12 ;返回%12;}
}
assign的cpp文件
#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"#include <iostream>
#include <memory>#ifdef DEBUG // 用于調試信息,大家可以在編譯過程中通過" -DDEBUG"來開啟這一選項
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl; // 輸出行號的簡單示例
#else
#define DEBUG_OUTPUT
#endif#define CONST_INT(num) \ConstantInt::get(num, module)#define CONST_FP(num) \ConstantFP::get(num, module) // 得到常數值的表示,方便后面多次用到int main()
{auto module = new Module("Cminus code"); // module name是什么無關緊要auto builder = new IRBuilder(nullptr, module);Type *Int32Type = Type::get_int32_type(module);auto mainFunTy = FunctionType::get(Int32Type, {}); // 通過返回值類型與參數類型列表得到函數類型auto mainFun = Function::create(mainFunTy, "main", module); // 通過函數類型得到函數auto bb = BasicBlock::create(module, "entry", mainFun);builder->set_insert_point(bb); // 將當前插入指令點的位置設在bb//int a[10];auto *arrayType = ArrayType::get(Int32Type, 10); // 在內存中為數組分配空間,參數表示數組的元素類型和元素個數auto aAlloca = builder->create_alloca(arrayType);//a[0]=10;auto a0GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(0)});// 獲取a[0]地址builder->create_store(CONST_INT(10), a0GEP); // a[0]=10a0GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(0)}); // 更新a[0]//a[1] = a[0] * 2;auto a0Load = builder->create_load(a0GEP); // 加載a[0]auto a0mul2 = builder->create_imul(a0Load, CONST_INT(2)); // a[0]*2auto a1GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(1)});// 獲取a[1]地址builder->create_store(a0mul2, a1GEP); // 將a[0]*2存入a[1]a1GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(1)}); // 更新a[1]//return a[1];auto a1Load = builder->create_load(a1GEP); // 加載a[1]builder->create_ret(a1Load); // 返回a[1]std::cout << module->print();delete module;return 0;
}
full的cpp文件
#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"#include <iostream>
#include <memory>#ifdef DEBUG // 用于調試信息,大家可以在編譯過程中通過" -DDEBUG"來開啟這一選項
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl; // 輸出行號的簡單示例
#else
#define DEBUG_OUTPUT
#endif#define CONST_INT(num) \ConstantInt::get(num, module)#define CONST_FP(num) \ConstantFP::get(num, module) // 得到常數值的表示,方便后面多次用到int main()
{auto module = new Module("Cminus code"); // module name是什么無關緊要auto builder = new IRBuilder(nullptr, module); // 創建IRBuilderType *Int32Type = Type::get_int32_type(module);// callee函數,創建函數std::vector<Type *> Ints(1, Int32Type); /* 函數參數類型的vector,內含1個int類型 */auto calleeFunTy = FunctionType::get(Int32Type, Ints); /* 通過返回值類型與參數類型列表得到函數類型 */auto calleeFun = Function::create(calleeFunTy, /* 由函數類型得到函數 */"callee", module);auto bb = BasicBlock::create(module, "fun", calleeFun); /* 創建基本塊,命名為fun */builder->set_insert_point(bb); /* 將基本塊插入builder中 */// 傳參auto aAlloca = builder->create_alloca(Int32Type); /* 在內存中分配參數a的位置 */std::vector<Value *> args; /* 獲取callee函數的形參,通過Function中的iterator */for (auto arg = calleeFun->arg_begin(); arg != calleeFun->arg_end(); arg++){args.push_back(*arg); // * 號運算符是從迭代器中取出迭代器當前指向的元素}builder->create_store(args[0], aAlloca); /* 存儲參數a */// 具體執行auto aLoad = builder->create_load(aAlloca); /* 將參數a存到變量aLoad中 */auto res = builder->create_imul(aLoad, CONST_INT(2)); /* 將值乘以2存入變量res中 */builder->create_ret(res); /* 創建返回,將res返回 */// main函數auto mainFun = Function::create(FunctionType::get(Int32Type, {}), /* 創建 main 函數 */"main", module);bb = BasicBlock::create(module, "main", mainFun); /* 創建基本塊,命名為main */builder->set_insert_point(bb); /* 將基本塊加入到builder中 */// 設置默認返回auto retAlloca = builder->create_alloca(Int32Type); /* 創建返回默認量 */builder->create_store(CONST_INT(0), retAlloca); /* 給默認量賦0,表示默認ret 0 */// 具體執行auto call = builder->create_call(calleeFun, {CONST_INT(110)}); /* 調用函數calleeFun,將結果存到變量call中 */builder->create_ret(call); /* 返回結果值 */std::cout << module->print();delete module;return 0;
}
if的cpp文件
#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"#include <iostream>
#include <memory>#ifdef DEBUG // 用于調試信息,大家可以在編譯過程中通過" -DDEBUG"來開啟這一選項
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl; // 輸出行號的簡單示例
#else
#define DEBUG_OUTPUT
#endif#define CONST_INT(num) \ConstantInt::get(num, module)#define CONST_FP(num) \ConstantFP::get(num, module) // 得到常數值的表示,方便后面多次用到int main() {auto module = new Module("Cminus code"); // module name是什么無關緊要auto builder = new IRBuilder(nullptr, module); // 創建IRBuilderType* Int32Type = Type::get_int32_type(module);// main函數auto mainFun = Function::create(FunctionType::get(Int32Type, {}), /* 創建 main 函數 */"main", module);auto bb = BasicBlock::create(module, "main", mainFun); /* 創建基本塊,命名為main */builder->set_insert_point(bb); /* 將基本塊加入到builder中 */// 設置默認返回auto retAlloca = builder->create_alloca(Int32Type); /* 創建返回默認量 */builder->create_store(CONST_INT(0), retAlloca); /* 給默認量賦0,表示默認ret 0 */// 具體執行Type* FloatType = Type::get_float_type(module); /* 獲取單個float類型的指針 */auto aAlloca = builder->create_alloca(FloatType); /* 根據float類型的指針,申請一個float變量空間 */builder->create_store(CONST_FP(5.555), aAlloca); /* 將值5.555存入該變量空間 */auto a = builder->create_load(aAlloca); /* 取出該變量空間內的值,即a的值 */auto fcmp = builder->create_fcmp_gt(a, CONST_FP(1.00)); /* 將其和1.00進行比較,返回結果存到fcmp中 */auto trueBB = BasicBlock::create(module, "trueBB", mainFun);/* 符合if條件的分支 */auto falseBB = BasicBlock::create(module, "falseBB", mainFun); /* 不符合if條件的分支 */builder->create_cond_br(fcmp, trueBB, falseBB); /* 根據fcmp創建跳轉語句 */builder->set_insert_point(trueBB); // if true; 分支的開始需要SetInsertPoint設置builder->create_ret(CONST_INT(233)); /* 創建返回,將值233返回 */builder->set_insert_point(falseBB); // if false; 分支的開始需要SetInsertPoint設置builder->create_ret(CONST_INT(0)); /* 創建返回,將值0返回 */std::cout << module->print();delete module;return 0;
}
while的cpp文件
#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"#include <iostream>
#include <memory>#ifdef DEBUG // 用于調試信息,大家可以在編譯過程中通過" -DDEBUG"來開啟這一選項
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl; // 輸出行號的簡單示例
#else
#define DEBUG_OUTPUT
#endif#define CONST_INT(num) \ConstantInt::get(num, module)#define CONST_FP(num) \ConstantFP::get(num, module) // 得到常數值的表示,方便后面多次用到int main() {auto module = new Module("Cminus code"); // module name是什么無關緊要auto builder = new IRBuilder(nullptr, module); // 創建IRBuilderType* Int32Type = Type::get_int32_type(module);// main函數auto mainFun = Function::create(FunctionType::get(Int32Type, {}), /* 創建 main 函數 */"main", module);auto bb = BasicBlock::create(module, "main", mainFun); /* 創建基本塊,命名為main */builder->set_insert_point(bb); /* 將基本塊加入到builder中 */// 設置默認返回auto retAlloca = builder->create_alloca(Int32Type); /* 創建返回默認量 */builder->create_store(CONST_INT(0), retAlloca); /* 給默認量賦0,表示默認ret 0 */// 創建基本塊auto whileBB = BasicBlock::create(module, "whileBB", mainFun); /* 進行while判斷的基本塊 */auto trueBB = BasicBlock::create(module, "trueBB", mainFun); /* 符合判斷條件的基本塊分支 */auto falseBB = BasicBlock::create(module, "falseBB", mainFun); /* 不符合判斷條件的基本塊分支 */// 具體執行auto aAlloca = builder->create_alloca(Int32Type); /* 申請存a的空間,將地址賦值給指針aAlloca */auto iAlloca = builder->create_alloca(Int32Type); /* 申請存i的空間,將地址賦值給指針iAlloca */builder->create_store(CONST_INT(10), aAlloca); /* 將值10存入a的空間 */builder->create_store(CONST_INT(0), iAlloca); /* 將值0存入i的空間 */builder->create_br(whileBB); /* 跳轉到while循環條件判斷,判斷是否進入循環 */builder->set_insert_point(whileBB); /* while條件判斷,設置SetInsertPoint */auto i = builder->create_load(iAlloca); /* 取出i */auto icmp = builder->create_icmp_lt(i, CONST_INT(10)); /* 判斷i是否小于10,并將判斷結果存到icmp中 */builder->create_cond_br(icmp, trueBB, falseBB); /* 根據icmp創建跳轉語句 */builder->set_insert_point(trueBB); // if true; 分支的開始需要SetInsertPoint設置i = builder->create_load(iAlloca); /* 取出i */auto tmp = builder->create_iadd(i, CONST_INT(1)); /* 將i加1,存到暫存變量tmp中,tmp=i+1 */builder->create_store(tmp, iAlloca); /* 將tmp的值存到i中,i=tmp*/auto a = builder->create_load(aAlloca); /* 取出a */i = builder->create_load(iAlloca); /* 取出i */tmp = builder->create_iadd(a, i); /* 將a加i的值存到tmp中,tmp=i+a */builder->create_store(tmp, aAlloca); /* 將tmp存到a中,a=tmp */builder->create_br(whileBB); /* 跳轉到while循環條件判斷,判斷是否繼續循環 */builder->set_insert_point(falseBB); // if false; 分支的開始需要SetInsertPoint設置auto res = builder->create_load(aAlloca); /* 取出a的值,存到res中,res=a */builder->create_ret(res); /* 將res返回,即return res */std::cout << module->print();delete module;return 0;
}