項目的完整代碼在 C2j-Compilerjava
第十一篇,終於要進入代碼生成部分了,可是可是在此以前,由於咱們要作的是C語言到字節碼的編譯,因此天然要了解一些字節碼,可是因爲C語言比較簡單,因此只須要了解一些字節碼基礎git
JVM有一個執行環境叫作stack framegithub
這個環境有兩個基本數據結構數組
還有一個PC指針,它指向下一條要執行的指令。數據結構
int f(int a, int b) { return a+b; } f(1,2);
JVM的執行環境是這樣變化的jvm
stack: localarray:1,2 pc:把a從localarray取出放到stack
stack:1 localarray:2 pc:把b從localarray取出放到stack
stack:1,2 localarray: pc:把a,b彈出堆棧而且相加壓入堆棧
.class public CSourceToJava .super java/lang/Object .method public static main([Ljava/lang/String;)V getstatic java/lang/System/out Ljava/io/PrintStream; ldc "Hello World!" invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V return .end method .end class
getstatic、ldc和invokevirtual都至關於JVM提供的指令函數
getstatic和ldc至關於壓入堆棧操做。invokevirtual則是從堆棧彈出參數,而後調用方法oop
stack: out "Hello World!"
JVM的運行基本都是圍繞着堆棧來進行,因此指令也都是和堆棧相關,好比進行一個乘法1 * 2:this
bipush 1 bipush 2 imul
能夠看到JVM的指令操做時帶數據的類型,b表明byte,也就是隻能操做-128 ~ 128之間的數,而i表明是整形操做,因此相應也會有sipush等等了指針
下面加入要把1 * 2打印用prinft打印在控制檯上,就須要把out對象壓入堆棧,此時的堆棧:
stack: 2 out
可是調用out的參數須要在堆棧頂部,因此這時候就須要兩個指令iload、istore
istore 0把2放到局部變量隊列,再把out壓入堆棧,再用iload 0把2放入堆棧中
stack: out 2
在字節碼裏,局部變量和函數參數都會存儲在隊列上
int func() { int a; int b; a = 1; b = 2; return a + b; }
看一下這個方法執行的時候堆棧的變化狀況
// 執行a = 1,把1壓到stack上,再把1放入到隊列裏 stack: array:1 // 執行b = 1,也同理 stack: array:1, 2
最後的return也有相應的return指令,因此完整的指令以下
sipush 1 istore 0 sipush 2 istore 1 iload 0 iload 1 iadd ireturn
int func(int a, int b, int c, int d){}
在調用這個函數的適合,函數參數就會按照順序被壓入堆棧中,而後拷貝到隊列上
stack: a b c d array: stack: array: d c b a
因此在以後的代碼生成部分就須要一個來找到局部變量的位置的函數
下面這段指令的做用是建立一個大小爲100的整形數組
sipush 100 newarray int astore 0
下面這段指令是讀取數組的第66個元素
aload 0 sipush 66 iaload
aload 0 sipush 7 sipush 10 iastore
C語言裏的結構體其實就至關於沒有方法只有屬性的類,因此能夠把結構體編譯成一個類
new MyClass //建立一個名字爲MyClass的類 invokespecial ClassName/<init>() V //調用類的無參構造函數
public class MyClass { public int a; public char c; public MyClass () { this.a = 0; this.c = 0; } }
public class MyClass生成下面的代碼,都是對應生成一個類的特殊指令
.class public MyClass .super java/lang/Object
下面的則是對應屬性的聲明
.field public c C .field public a I
聲明完屬性,就是構造函數了,首先是先把類的實例加載到堆棧,再調用它的父類構造函數,對屬性的賦值:
aload 0 invokespecial java/lang/Object/<init>()V aload 0 sipush 0 putfield MyClass/c C aload 0 sipush 0 putfield MyClass/a I return
完整的對應的Java字節碼以下:
.class public MyClass .super java/lang/Object .field public c C .field public a I .method public <init>()V aload 0 invokespecial java/lang/Object/<init>()V aload 0 sipush 0 putfield MyClass/c C aload 0 sipush 0 putfield MyClass/a I return .end method .end class
aload 3 ;假設類實例位於局部變量隊列第3個位置 putfield ClassName/x I
下面的指令建立了10個字符串類型的數組,這時候堆棧上的對象是一個引用,指向heap上一個10個字符串類型的數組
sipush 10 anewarray java/lang/String
下面的指令則是對數組的第一個元素進行賦值
astore 0 aload 0 sipush 0 ldc "hello world" aastore
因此對於咱們本身定義的類也是同樣的
sipush 10 anewarray MyClass astore 0
下面則是對數組第一個下標生成一個MyClass對象
aload 0 sipush 1 new MyClass invokespecial CTag/<init>()V aastore
下面是對數組裏的對象的屬性的取值和賦值操做,只是組合了以前的指令而已
aload 0 sipush 1 aaload sipush 1 putfield MyClass/x I aload 0 sipush 1 aaload getfield MyClass/x I
JVM指令還有兩個個很是重要的指令就是分支和循環指令,咱們先來看分支指令
if (1 < 2) { a = 1; } else { a = 2; }
上面對應的JVM指令以下:
sipush 1 sipush 2 if_cmpge branch0 sipush 1 astore 0 goto out_branch0 branch0: sipush 2 istore 0 out_branch0: sipush 3 istore 0
基本的JVM指令只剩循環語句了,邏輯也不困難,基本的JVM指令相對於彙編算是很是簡單了
for (i = 0; i < 3; i++) { a[i] = i; }
上面生成的對應字節碼以下(假設如今變量i在隊列的第5個位置,a在隊列的第2個位置):
sipush 0 istore 5 loop0: iload 5 sipush 3 if_icmpge branch0 aload 2 ;加載數組 iload 3 ;加載標i iload 3 ;加載變量i iastore ;把i的值存入到a[i] iload 3 ;加i sipush 1 ;把1壓入堆棧 iadd ;i++ istore 3 ;把i+1後的值放入到i的隊列上的位置 goto loop0 ;跳轉到循環開頭 branch0:
這一篇主要就是了解一下Java基本的字節碼,由於C語言的語法比較簡單,因此只須要知道一點就足夠生成代碼了。因此相對於彙編來講,是很是簡單的了。這樣下一篇就能夠正式進入代碼生成部分
另外,歡迎Star這個項目!