從零寫一個編譯器(十一):代碼生成之Java字節碼基礎

項目的完整代碼在 C2j-Compilerjava

前言

第十一篇,終於要進入代碼生成部分了,可是可是在此以前,由於咱們要作的是C語言到字節碼的編譯,因此天然要了解一些字節碼,可是因爲C語言比較簡單,因此只須要了解一些字節碼基礎git

JVM的基本機制

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彈出堆棧而且相加壓入堆棧

對於JVM提供的對象

.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的基本指令

pusu load store

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
  • sipush 100 把元素個數壓入堆棧
  • newarray int 建立一個數組,後面是數據類型
  • astore 表示把數組對象移入隊列 a表示的是一個對象引用

讀取數組

下面這段指令是讀取數組的第66個元素

aload 0
sipush 66
iaload
  • aload 0 把數組對象放到堆棧上
  • sipush 放入要讀取的元素下標
  • iaload 把讀取的值壓入堆棧

元素賦值

aload 0
sipush 7
sipush 10
iastore
  • 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

聲明完屬性,就是構造函數了,首先是先把類的實例加載到堆棧,再調用它的父類構造函數,對屬性的賦值:

  1. 加載類的實例到堆棧上 aload 0
  2. 壓入值 sipush 0
  3. 賦值的對應指令 putfield MyClass/c C
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指令以下:

  • 先把1和2壓入堆棧
  • if_cmpge指令是大於等於,即若是1大於等於2就去執行else分支
  • goto指令是跳轉到相應的標籤,也就是執行完if,就跳出else部分
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個位置):

  • 首先對i賦值
  • 再把3壓入堆棧和i作比較,判斷i < 3
  • 以後就是對數組的操做
  • 而後修改i的值
  • 返回loop0繼續判斷i < 3
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這個項目!

相關文章
相關標籤/搜索