05.從0實現一個JVM語言之目標平臺程式碼生成-CodeGenerator
阿新 • • 發佈:2021-03-05
從0實現JVM語言之目標平臺程式碼生成-CodeGenerator
===
## [原始碼github倉庫, 如果這個系列文章對你有幫助, 希望獲得你的一個star](https://github.com/MilitaryIntelligence6/Cva)
## [本節相關程式碼生成package地址](https://github.com/MilitaryIntelligence6/Cva/tree/master/Cvac/src/main/java/cn/misection/cvac/codegen)
## [系列導讀 00.一個JVM語言的誕生](https://www.cnblogs.com/misection/p/14429145.html)
## 階段性的告別
```text
非常感謝您能看到這裡, 您的陪伴是我這幾天堅持寫作整合的動力! 但是所有的故事都有畫上句號
的那一天, 我們這個系列也不例外, 今天就是一個給大家的階段性停更, 因為我們程式碼優化部分以及一些
還沒實現但是應該實現的部分(陣列, printf等等)還會繼續實現, 還有程式碼中有的編譯優化可能也要隔
一段時間才能獻上了, 主要原因是目前由於個人要準備春招實習了, 要暫時跟大家說一聲告別, 今天這篇
結束以後可能會停更一段時間, 春招完後會繼續更新這個系列以及其他系列
之後這個系列還會頻率更低地更新, 未來我打算出一個手寫TCP/IP協議簇系列, 再出一個手寫簡單的
作業系統系列, 手寫JVM系列, 如果時間足夠, 應該還會出手寫Tomcat系列, 哈哈這些太多了, 但是以前
沒有深入瞭解很遺憾, 在這之前應有一個Android系列, Android應該就是寫個部落格園/github客戶端, 安
卓真的是我一直想求而不得, 學校選課移動開發(Android), 我連選三個學期, 前兩個學期直接課程衝了,
第三個學期我能選的那個時間課程只有5個人選, 被撤銷了, 我已經哭了, 學分已經修滿了,以後大概率是
不會再選這課的, 因為時間該實習了 , 後面Android系列打算用Kotlin實現, 一些很前衛的巨頭就是Kotlin
first(據說位元組就是), Kotlin的函式式, 協程以及空安全是更先進更舒服的, 當然個人也簡單瞭解過一點
flutter, 所以到時候傾向於用Kotlin或者Flutter去完成這個專案
更新的系列可能與複習同步, 當做複習練手, 但也可能暫時不會更新了, 要看我個人的複習進度, 我覺得
大概率是鴿, 不過大家放心, 過了春招這段時間我會繼續堅持分享個人一些有趣的, 比較精心打磨, 完成度較好
的專案的
非常感謝一些老讀者的陪伴(雖然應該只有個位數的幾位朋友), 是你們的堅持閱讀才讓我堅持寫完了這個系列,
也督促我不能每天都起碼更個新, 不水大家
也非常感謝您能忍受我一直以來自己都覺得醜的文筆和敘述, 哈哈, 您有什麼寶貴意見都可以留言,
我會盡我所能改正或者提供支援
所以專案就將時停止更新了, 讓我們有緣再會!
```
```text
致親愛的讀者:
個人的文字組織和寫文章的功底屬實一般, 寫的也比較趕時間, 所以系列文章的文字可能比較粗糙,
難免有詞不達意或者寫的很迷惑抽象的地方
如果您看了有疑問或者覺得我寫的實在亂七八糟, 這個很抱歉, 確實是我的問題, 您如果有不懂的地方
的地方或者發現我的錯誤(文字錯誤, 邏輯錯誤或者知識點錯誤都有可能), 可以直接留言, 我看到都會回覆您!
```
## **系列食用方法建議**
```text
由於時間原因, 目前測試並不完善, 所以推薦如下方式根據您的目的進行閱讀
如果您是學習用, 建議您先將整個專案clone到本地, 然後把感興趣的章節刪除, 自己重寫對照著重寫
書寫完每一步測試一下能否正常執行(在指定的路徑去讀取原始碼測試能否編譯成功並在命令列執行
java Application(類名)
嘗試能否輸出期望結果, 我沒有研究Junit對編譯器輸出class檔案進行測試, 所以目前可能需要您手動測試)
按照以上步驟, 等您將所有模組重寫一遍, 大概也對這個系列的脈絡有深刻理解了! 如果您重頭開始重寫,
往往可能由於出現某些低階錯誤導致長時間debug才找得到錯誤, 所以對於初學者, 推薦採用自己補寫替換模組的
方式
對於希望貢獻程式碼的朋友或者對Cva感興趣的朋友, 歡迎貢獻您的原始碼與見解, 或者對於該系列一些錯誤/
bug願意提出指正的朋友, 您可以留言或者在github發issue, 我看到後一定及時處理!
```
本節提綱
---
1. 引言-編譯器前端的尾聲
2. BST(Backend Abstract Syntax Tree)
3. Translator
4. JVM指令
4.1. 我們使用的指令介紹
5. Jasmin 彙編器生成 .class File
## 引言-編譯器前端的尾聲
在該步之前還應有編譯期優化, 但是由於時間原因沒有完善, debug時也一直沒有開優化, 所以這部分以後有緣再
補充
程式碼生成是編譯器前端的最後一步了, Javac編譯器的編譯工作到這一步, 便是將Java原始碼編譯成JVM .class
位元組碼檔案, 而我們這個階段也是如此, 其實當我們拿到抽象語法樹, 可以玩一點花的, 可以將Cva位元組碼編譯到
Java, Csharp, C語言甚至js等等
這是因為我們拿到的抽象語法樹已經是一棵可執行的樹了, 我們得到的原始碼文字轉換得到的Program POJO是一個
有靈魂的POJO, 他能做的事就是你寫程式碼時所表達的那些事, 當然, 我們也可以為其開發一個直譯器, 執行這課
語法樹(當這個直譯器的構造與計算機類似, 拓展了許多與作業系統類似的功能如垃圾回收/JIT時, 其也可以叫做
虛擬機器), 當然, 這裡我們並不教大家如何去做這些工作, 如果你有興趣可以嘗試做一下, 我們這裡就做一些大家
喜聞樂見的, 常規的編譯器做法, 生成可執行的指令
如果是直接面向機器的編譯器, 那麼這個時候往往會生成指定平臺的彙編(8086彙編, x86彙編)再進行接下來一步
的生成, 我們這裡並不是直接生成JVM位元組碼(JVM平臺 .class檔案), 而是先生成JVM平臺中間語言(Intermediate
Language)JVM彙編指令, 再由彙編器(由中間語言/組合語言生成機器碼, 這個過程基本就是對映關係, 所以彙編指
令也叫助記符), 我們這裡其實也是面向機器, 不過這臺機器是一個特殊的JVM虛擬機器, JVM虛擬機器其實也有著自己的
一套匯編指令集及規範
當然, 在編譯器後端, 有著更為豐富精彩的世界, 比如執行時的編譯, JIT, AOT等等, 還有著無底洞般的優化, 希望
以後有機會能給大家展現這些東西
同時, 這一節由於時間原因, 和多次重構的歷史原因, 程式碼結構相對有一點亂, 個人將梳理講解值得一些注意的部分
## BST(Backend Abstract Syntax Tree)
cn.misection.cva.ast 是表示我們前端Cva源程式的抽象語法樹, 但是這個東西表示的畢竟是我們自己定義的
Cva程式, 而不是我們的目標平臺JVM可以識別的程式, 或者說, 跟我們目標有差異, 我們不可能直接將這棵語法樹
用來在JVM平臺執行或者編譯到JVM上(當然我們可以針對這種程式結構開發直譯器, 讓其在我們的直譯器/虛擬機器上執行)
後端抽象語法樹, 說是後端語法樹, 其實過程還是屬於前端, 只不過這裡更接近虛擬機器後端, 而且我們的
語法樹由前端的樹形被我們翻譯成了後端的線性指令形式, 語法結構劇變, 所以我們需要一個 新的BST作為從AST
到指令的中間表示. BST主要關注點在Statement和Expression方面
## Translator
說道 Translator, 顧名思義, 大家應該也能理解它的作用, 這是一個翻譯官, 能將我們前端的ast翻譯成後端的bst,
將樹狀的Cva程式翻譯成線性的JVM指令, 他接受一個前端傳入的CvaProgram, 生成一個後端的TargetProgram, 然
後交由IntermLangGenerator寫入.il中間語言檔案, 最後再交由Cvac 編譯器的main方法靜態呼叫jasmin的主方法
生成我們的.class位元組碼, 我們的編譯器工作就完成啦!
其實現是impl後端的visitor, 這裡使用到了(偽)訪問者模式, 是一個設計的不成功的訪問者模式, 大家看看就好,
我以後有更好的方案會重構他
## JVM指令
由於JVM是基於棧式計算機概念的虛擬機器, 以JVM為目標平臺的程式碼生成較為簡單, 不必考慮暫存器分配等問題; 同時JVM
還有著豐富的指令
我們的語言由於支援的型別很有限, 支援的基本型別只有`int`和`boolean`(其實這倆都是int), 其他型別暫時沒有過多
處理, 以及涉及到基本型別之間互相轉換的操作指令可以不用考慮,我們支援的比較也比較少, 因此跳轉指令可以只考慮部分,
由於不存在一些特殊型別或者操作符, 比如`interface` `abstract` `instanceof`等等複雜的指令
此外, JVM規範中有:
Java Virtual Machine Specification - 2.3.4 The `boolean` type
> Although the Java Virtual Machine defines a boolean type, it only provides
very limited support for it. There are no Java Virtual Machine instructions solely
dedicated to operations on boolean values. Instead, expressions in the Java
programming language that operate on boolean values are compiled to use values
of the Java Virtual Machine int data type.
其實後端是沒有boolean的, 直接用int 型 0 1 表示即可, 這也是為啥C語言只有0和非0的原因了,
感覺我們似乎越來越接近世界的真相了, 哈哈
### 我們使用的指令介紹
按照JVM規範, JVM支援共計大約150個指令, 我們用到的指令僅僅是相當小的一個子集, 先給出我們使用的指令
```text
aload
areturn
astore
getfield
goto