【HUST】网安|编译原理实验|实验四攻略
【實驗代碼及報告地址:Gitee傳送門】(已關閉傳送大門,原因是抄襲過多,如需參考,請直接看博客,雖然下一屆內(nèi)容會變了)
不擅長寫報告昂,很多地方能省全省了。
助力來年編譯原理加大難度!(hhh)
MiniC語法分析及中間代碼生成
我根據(jù)我的實驗報告重置了攻略。
貼個完成時間。
文章目錄
- MiniC語法分析及中間代碼生成
- 實驗內(nèi)容
- 實驗過程
- LLVM IR初識
- LLVM IR API
- 語義分析與中間代碼生成(一)
- 難點
- 調(diào)試技巧
- Quick Start
- 正式開始
- 1. astnode.cpp需要修改的函數(shù)
- 2. astnode.h需要修改的函數(shù)
- 3. 一個分析過程
- 4. 舉幾個典型的例子
- 4.1. 各種List
- 4.2 錯誤判斷
- 語義分析與中間代碼生成(二)
- 1. astnode.cpp需要修改的函數(shù)
- 2. 難點
- 2.1 一些函數(shù)
- 2.2 NArgs如何解析
- 3. 錯誤判斷
- 語義分析與中間代碼生成(三)
- 1. astnode.cpp需要修改的函數(shù)
- 2. astnode.h需要修改的函數(shù)
- 3. 分析思路
- 3.1 if-else結(jié)構(gòu)
- 3.2 變量的作用域問題
- 3.3 或運算||的解析
實驗內(nèi)容
將Mini-C源程序,手工翻譯或用LLVM API翻譯成LLVM IR(一種中間語言),并完成語法檢查。
實驗過程
LLVM IR初識
結(jié)合給出的例子,認識并熟悉LLVM IR的語法。
主要涉及了align/alloca/store/load/call/icmp/br等。【可以自行結(jié)合代碼展開說說】
主要是根據(jù)Mini-C語言翻譯,有兩個地方對我造成了困擾:【相信每個人困擾的點不一樣,自圓其說即可】
- task2中我一開始將putchar寫進了判斷體中,導致狀態(tài)不夠用,不得不移出。
- task2中涉及標號label。不像asm,llvm的標號上面不能沒有跳轉(zhuǎn)語句直接順序執(zhí)行,它必須添加br label %8跳轉(zhuǎn)。
LLVM IR API
熟悉并使用LLVM IR API,主要涉及了llvm::IRBuilderBase類的API,還有一些Constant、BasicBlock、Type類的API。【可以自行結(jié)合代碼展開說說】
難點:【相信每個人的難點不一樣,自圓其說即可】
- getchar是無參數(shù)函數(shù),而builder->CreateCall需要接受1到3個參數(shù),直接傳遞空vector行不通。
正解:builder->CreateCall(callgetfunc); - task2中if-then-else涉及3個基本塊,樣例中是if-then,只涉及兩個模塊。而且并不好理解。
正解:根據(jù)官網(wǎng)的IF-THEN-ELSE寫,它的解釋很好。MyFirstLanguageFrontend/LangImpl05.html#code-generation-for-if-then-else。 - 一些個人遇到的問題:在進入第三個基本塊我沒有
f->getBasicBlockList().push_back(MergeBB);,因為我以為到結(jié)尾了可以直接順序執(zhí)行下去,犯了和關卡1相似的錯誤。沒有寫這一句時,報錯會一直提示then:錯誤,導致我一直沒定位到真正的錯誤位置。
語義分析與中間代碼生成(一)
【因為實在很長,所以實驗報告里我沒有寫過程,只寫了難點。不建議大家這樣做,因為每個人的難點都是不一樣的】
【如果沒話寫,可以著重寫一下“錯誤判斷”部分】
難點
- 咋聲明變量?(答:抄NExtDefFunDec::codegen的arg聲明)
- 我怎么知道一個函數(shù)里需要解析哪些參數(shù)、哪些參數(shù)是需要被特殊處理的?(答:看上面的分析,對照parser.y和astnode.h)
- 咋把分析結(jié)果存到變量里面?(答:實驗二里做過,不記得的話,函數(shù)聲明如是:
builder->CreateStore(Value, alloca);) - 咋分析加?(答:實驗二有提示。不記得的話,函數(shù)聲明如是:
builder->CreateAdd(left, right, "add");) - abort怎么炸了,返回之后就提示我
core dump?
答:請檢查是不是把nullptr輸入到一個函數(shù)中了,舉個簡單的例子(摘自NAssignment::codegen函數(shù)):
再檢查是否未初始化指針就直接用了,舉個retValue沒初始化就返回的例子:
Value* retValue; if(false) retValue=nullptr; return retValue; - module找不到,它什么意思?(答:字面意思,找不到就找不到。執(zhí)行fundec成功了就能找到main)
- 為了實現(xiàn)功能,我參考函數(shù)定義的部分,即ExtDefFunc::codegen,添加了一個繼承屬性type,如函數(shù)
NDef。
調(diào)試技巧
std::cout調(diào)試,定位出錯的位置。cat tokens.txt調(diào)試,通過閱讀生成的中間代碼,判斷語義分析的哪一步出了問題,或者根本沒解析出來。vim ./task1case/1.in修改樣例調(diào)試。當你覺得是某個邏輯寫得很有問題,但是樣例太長了,中間代碼眼花繚亂,就直接修改樣例。p->parser();輸出語法樹。將main.cpp中的p->parser();注釋掉,再編譯運行樣例,能看到每個樣例完整的語法樹。建議順著語法樹的解析順序,逐個填充codegen,每填充完一個,就去編譯運行試試。
Quick Start
使用頭歌命令行調(diào)試
打開頭歌,打開命令行,進入對應的代碼倉庫。
cd /data/workspace/myshixun/llvmexp3
檢查一下當前目錄下的文件。
ls
應該能看到judge_taskx.sh文件,以第三關為例,看一下judge_task1.sh的內(nèi)容。
具體內(nèi)容就不貼了,總之是這樣用的:
- 先編譯(make)成
./minic文件,它是一個可執(zhí)行的文件,用來生成中間代碼LLVM IR。
make
中間代碼LLVM IR:類似于
br i1 %shandian7, label %then8, label %ifcont10這種東西。
- 在代碼倉庫下有測試樣例,以第3關為例,對應
task1case。 - 用minic編譯一個樣例,并將中間代碼(或報錯)存儲至
tokens.txt。
./minic ./task1case/0.in > tokens.txt
- 如果生成中間代碼成功了,
cat tokens.txt看一下輸出(你在cpp里寫的std::cout、輸出的報錯Error信息也會在這個txt里)。 - 如果這是一個正確的中間代碼,而且它需要編譯運行,并接收一定的輸入,請這樣:
echo "你要給程序輸入的值(就是stdin)" > tmp
lli tokens.txt < tmp
正式開始
1. astnode.cpp需要修改的函數(shù)
正好十個。
NIdentifier蠻麻煩的,如下:
注:寫第三題的時候可以亂來,寫第五題就得考慮作用域了。
2. astnode.h需要修改的函數(shù)
寫第五關的時候發(fā)現(xiàn),VarDec::codegen返回值設為AllocaInst *才行。
3. 一個分析過程
4. 舉幾個典型的例子
4.1. 各種List
直到第五題才能發(fā)現(xiàn)的錯誤:注意一個特殊的,NDec::codegen,根據(jù)Dec: VarDec | VarDec ASSIGNOP Exp,它的Exp是要用來給VarDec的alloca賦值的,不要只是單純地解析它然后就不管了喔。
4.2 錯誤判斷
++ 錯誤類型 1:變量在使用時未經(jīng)定義。
思路就是調(diào)用的時候找找curNamedValues里有沒有。(摘自NAssignment::codegen函數(shù))
++ 錯誤類型 2:函數(shù)在調(diào)用時未經(jīng)定義。
思路就是調(diào)用的時候找找theModule里有沒有。(摘自NMethodCall::codegen函數(shù))
++ 錯誤類型 3:變量出現(xiàn)重復定義。
思路就是聲明的時候找找curNamedValues里有沒有。(摘自NExtDefFunDec::codegen函數(shù))
語義分析與中間代碼生成(二)
有了第3關的基礎,第四關明顯簡單了許多。
【實驗報告我依舊只寫了難點,注意口語書面化】
【如果沒話寫,可以著重寫一下“錯誤判斷”部分】
1. astnode.cpp需要修改的函數(shù)
只有四個,非常快樂。
注:由于第3關的時候,我順手把NFloat::codegen之類的全部寫好了,所以這里沒有特意標注。
好像有同學找不到Float的取值函數(shù),如下:
2. 難點
2.1 一些函數(shù)
出現(xiàn)了很多老師沒給樣例的函數(shù)。
如CreateSub、getType()方法、getReturnType()
參考classllvm_1_1IRBuilderBase.html找到CreateSub函數(shù),
參考classllvm_1_1Function.html得知getReturnType(),
參考classllvm_1_1Value.html得知Value有自己的getType()函數(shù)。
我是如何查找的?【經(jīng)驗分享】
例如,我想獲得auto *類型的變量retValue的type。
- 很自然地寫出了
retValue.type的代碼,然后報錯,說retValue是個指針,建議用->來訪問它的成員。 - 于是訂正為
retValue->type,再次報錯,說class llvm::Value *沒有type成員。 - 那么
llvm::Value有什么呢?搜索llvm::Value,跳轉(zhuǎn)官網(wǎng),找到getType()函數(shù)。 - 訂正為
retValue->getType(),成功。
2.2 NArgs如何解析
引言:
- 明顯,NArgs對應多個參數(shù),但是它的codegen()只返回一個Value*,這不合適啊!
- 難道參考List函數(shù)的解析方式,修改NArgs::codegen(),依次展開?不行啊,每個返回值都要用,依次展開沒辦法返回回去啊!
正解:參考NFunDec::funcodegen中對NVarList的解析即可。
std::vector<Type *> argsTypes;
std::vector<std::string> argNames;
for (NVarList *item = arguments; item; item = item->nVarList) {
auto tmp = item->nParamDec.getType();
argNames.push_back(tmp.first);
argsTypes.push_back(tmp.second);
}
注意,exp解析得到的Value*的類型,直接用getType就可以取到了,很好寫,不要想太復雜了。
3. 錯誤判斷
++ 錯誤類型 4:函數(shù)出現(xiàn)重復定義(即同樣的函數(shù)名被多次定義)。
這個老師寫好了。在NFunDec::funcodegen里。
++ 錯誤類型 5:賦值號兩邊的表達式類型不匹配。
++ 錯誤類型 6:賦值號左邊出現(xiàn)一個只有右值的表達式。
++ 錯誤類型 7:return 語句的返回類型與函數(shù)定義的返回類型不匹配。
++ 銷誤類型 8:函數(shù)調(diào)用時實參與形參的數(shù)目或類型不匹配。
沒什么好說的叭,能寫出第三題沒理由寫不出錯誤判斷。
語義分析與中間代碼生成(三)
【報告只寫了分析思路部分】
1. astnode.cpp需要修改的函數(shù)
主要是因為我前面偷懶偷得比較多,所以改動也很多。
2. astnode.h需要修改的函數(shù)
這個地方真的是,我沒太動腦,返回值設錯了一通亂解析。
3. 分析思路
首先看樣例:
這個題沒有涉及任何錯誤判斷,需要完成的是算術運算+-*/和邏輯符號||,以及三個結(jié)構(gòu):if/ifelse/while。
3.1 if-else結(jié)構(gòu)
其中ifelse結(jié)構(gòu)直接參考實驗2的,對應的是NIfElseStmt::codegen,應該沒什么好說的吧。寫了這個之后樣例1就都過了。
樣例1:
int read(){
int a=0;
a = getchar();
return a - 48;
}
int main(){
int m,n;
int i=48;
m = read();
n = read();
if(m == n ){
putchar(i);
}else{
i = i + 1;
putchar(i);
}
i = i + 1;
putchar(i);
return 0;
}
3.2 變量的作用域問題
因為一些因素,我沒有用樣例分析,而是用了更復雜的深層結(jié)構(gòu),分析以及偽代碼如下。
3.3 或運算||的解析
提示:用基本塊。多看看生成的中間代碼和預期的區(qū)別。
貼一下報錯,這個報錯是因為while循環(huán)過多棧炸了,可能是因為條件判斷寫得有問題,心態(tài)穩(wěn)住。
總結(jié)
以上是生活随笔為你收集整理的【HUST】网安|编译原理实验|实验四攻略的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【记录】博客|Markdown写作常用的
- 下一篇: GStreamer开发笔记(四):ubu