从零写一个编译器(十一):代码生成之Java字节码基础
項目的完整代碼在 C2j-Compiler
前言
第十一篇,終于要進入代碼生成部分了,但是但是在此之前,因為我們要做的是C語言到字節(jié)碼的編譯,所以自然要了解一些字節(jié)碼,但是由于C語言比較簡單,所以只需要了解一些字節(jié)碼基礎(chǔ)
JVM的基本機制
JVM有一個執(zhí)行環(huán)境叫做stack frame
這個環(huán)境有兩個基本數(shù)據(jù)結(jié)構(gòu)
- 執(zhí)行堆棧:指令的執(zhí)行,都會圍繞這個堆棧來進行
- 局部變量數(shù)組,參數(shù)和局部變量就存儲在這個數(shù)組。
還有一個PC指針,它指向下一條要執(zhí)行的指令。
舉一個例子
int f(int a, int b) {return a+b; }f(1,2);JVM的執(zhí)行環(huán)境是這樣變化的
stack: localarray:1,2 pc:把a從localarray取出放到stack stack:1 localarray:2 pc:把b從localarray取出放到stack stack:1,2 localarray: pc:把a,b彈出堆棧并且相加壓入堆棧對于JVM提供的對象
.class public CSourceToJava .super java/lang/Object .method public static main([Ljava/lang/String;)Vgetstatic java/lang/System/out Ljava/io/PrintStream;ldc "Hello World!"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)Vreturn .end method .end classgetstatic、ldc和invokevirtual都相當于JVM提供的指令
getstatic和ldc相當于壓入堆棧操作。invokevirtual則是從堆棧彈出參數(shù),然后調(diào)用方法
stack: out "Hello World!"JVM的基本指令
pusu load store
JVM的運行基本都是圍繞著堆棧來進行,所以指令也都是和堆棧相關(guān),比如進行一個乘法1 * 2:
bipush 1 bipush 2 imul可以看到JVM的指令操作時帶數(shù)據(jù)的類型,b代表byte,也就是只能操作-128 ~ 128之間的數(shù),而i代表是整形操作,所以相應(yīng)也會有sipush等等了
下面加入要把1 * 2打印用prinft打印在控制臺上,就需要把out對象壓入堆棧,此時的堆棧:
stack: 2 out但是調(diào)用out的參數(shù)需要在堆棧頂部,所以這時候就需要兩個指令iload、istore
istore 0把2放到局部變量隊列,再把out壓入堆棧,再用iload 0把2放入堆棧中
stack: out 2局部變量和函數(shù)參數(shù)
局部變量
在字節(jié)碼里,局部變量和函數(shù)參數(shù)都會存儲在隊列上
int func() {int a;int b;a = 1;b = 2;return a + b; }看一下這個方法執(zhí)行的時候堆棧的變化情況
// 執(zhí)行a = 1,把1壓到stack上,再把1放入到隊列里 stack: array:1// 執(zhí)行b = 1,也同理 stack: array:1, 2最后的return也有相應(yīng)的return指令,所以完整的指令如下
sipush 1 istore 0 sipush 2 istore 1 iload 0 iload 1 iadd ireturn函數(shù)參數(shù)
int func(int a, int b, int c, int d){}在調(diào)用這個函數(shù)的適合,函數(shù)參數(shù)就會按照順序被壓入堆棧中,然后拷貝到隊列上
stack: a b c d array:stack: array: d c b a所以在之后的代碼生成部分就需要一個來找到局部變量的位置的函數(shù)
數(shù)組
創(chuàng)建數(shù)組
下面這段指令的作用是創(chuàng)建一個大小為100的整形數(shù)組
sipush 100 newarray int astore 0- sipush 100 把元素個數(shù)壓入堆棧
- newarray int 創(chuàng)建一個數(shù)組,后面是數(shù)據(jù)類型
- astore 表示把數(shù)組對象移入隊列 a表示的是一個對象引用
讀取數(shù)組
下面這段指令是讀取數(shù)組的第66個元素
aload 0 sipush 66 iaload- aload 0 把數(shù)組對象放到堆棧上
- sipush 放入要讀取的元素下標
- iaload 把讀取的值壓入堆棧
元素賦值
aload 0 sipush 7 sipush 10 iastore- aload 0 把數(shù)組對象加載到堆棧
- sipush 7 把要賦值的值壓入堆棧
- sipush 10 把元素下標壓入堆棧
- iastore 進行賦值
結(jié)構(gòu)體
C語言里的結(jié)構(gòu)體其實就相當于沒有方法只有屬性的類,所以可以把結(jié)構(gòu)體編譯成一個類
創(chuàng)建一個類
new MyClass //創(chuàng)建一個名字為MyClass的類 invokespecial ClassName/<init>() V //調(diào)用類的無參構(gòu)造函數(shù)例子
public class MyClass {public int a;public char c;public MyClass () {this.a = 0;this.c = 0;} }public class MyClass生成下面的代碼,都是對應(yīng)生成一個類的特殊指令
.class public MyClass .super java/lang/Object下面的則是對應(yīng)屬性的聲明
.field public c C .field public a I聲明完屬性,就是構(gòu)造函數(shù)了,首先是先把類的實例加載到堆棧,再調(diào)用它的父類構(gòu)造函數(shù),對屬性的賦值:
完整的對應(yīng)的Java字節(jié)碼如下:
.class public MyClass .super java/lang/Object .field public c C .field public a I .method public <init>()Vaload 0invokespecial java/lang/Object/<init>()Vaload 0sipush 0putfield MyClass/c Caload 0sipush 0putfield MyClass/a Ireturn .end method .end class讀取類的屬性
aload 3 ;假設(shè)類實例位于局部變量隊列第3個位置 putfield ClassName/x I結(jié)構(gòu)體數(shù)組
下面的指令創(chuàng)建了10個字符串類型的數(shù)組,這時候堆棧上的對象是一個引用,指向heap上一個10個字符串類型的數(shù)組
sipush 10 anewarray java/lang/String下面的指令則是對數(shù)組的第一個元素進行賦值
astore 0 aload 0 sipush 0 ldc "hello world" aastore所以對于我們自己定義的類也是一樣的
sipush 10 anewarray MyClass astore 0下面則是對數(shù)組第一個下標生成一個MyClass對象
aload 0 sipush 1 new MyClass invokespecial CTag/<init>()V aastore下面是對數(shù)組里的對象的屬性的取值和賦值操作,只是組合了之前的指令而已
aload 0 sipush 1 aaload sipush 1 putfield MyClass/x Iaload 0 sipush 1 aaload getfield MyClass/x I分支語句
JVM指令還有兩個個非常重要的指令就是分支和循環(huán)指令,我們先來看分支指令
if (1 < 2) {a = 1; } else {a = 2; }上面對應(yīng)的JVM指令如下:
- 先把1和2壓入堆棧
- if_cmpge指令是大于等于,即如果1大于等于2就去執(zhí)行else分支
- goto指令是跳轉(zhuǎn)到相應(yīng)的標簽,也就是執(zhí)行完if,就跳出else部分
循環(huán)語句
基本的JVM指令只剩循環(huán)語句了,邏輯也不困難,基本的JVM指令相對于匯編算是非常簡單了
for (i = 0; i < 3; i++) { a[i] = i; }上面生成的對應(yīng)字節(jié)碼如下(假設(shè)現(xiàn)在變量i在隊列的第5個位置,a在隊列的第2個位置):
- 首先對i賦值
- 再把3壓入堆棧和i做比較,判斷i < 3
- 之后就是對數(shù)組的操作
- 然后修改i的值
- 返回loop0繼續(xù)判斷i < 3
小結(jié)
這一篇主要就是了解一下Java基本的字節(jié)碼,因為C語言的語法比較簡單,所以只需要知道一點就足夠生成代碼了。所以相對于匯編來說,是非常簡單的了。這樣下一篇就可以正式進入代碼生成部分
另外,歡迎Star這個項目!
轉(zhuǎn)載于:https://www.cnblogs.com/secoding/p/11384619.html
總結(jié)
以上是生活随笔為你收集整理的从零写一个编译器(十一):代码生成之Java字节码基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从零写一个编译器(十):编译前传之直接解
- 下一篇: url映射 ccf (Java正则表达式