1. 程式人生 > >從零寫一個編譯器(十一):程式碼生成之Java位元組碼基礎

從零寫一個編譯器(十一):程式碼生成之Java位元組碼基礎

專案的完整程式碼在 C2j-Compiler

前言

第十一篇,終於要進入程式碼生成部分了,但是但是在此之前,因為我們要做的是C語言到位元組碼的編譯,所以自然要了解一些位元組碼,但是由於C語言比較簡單,所以只需要瞭解一些位元組碼基礎

JVM的基本機制

JVM有一個執行環境叫做stack frame

這個環境有兩個基本資料結構

  • 執行堆疊:指令的執行,都會圍繞這個堆疊來進行
  • 區域性變數陣列,引數和區域性變數就儲存在這個陣列。

還有一個PC指標,它指向下一條要執行的指令。

舉一個例子

int f(int a, int b) {
    return a+b;
}

f(1,2);

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則是從堆疊彈出引數,然後呼叫方法

stack: out "Hello World!"

JVM的基本指令

pusu load store

JVM的執行基本都是圍繞著堆疊來進行,所以指令也都是和堆疊相關,比如進行一個乘法1 * 2:

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

小結

這一篇主要就是了解一下Java基本的位元組碼,因為C語言的語法比較簡單,所以只需要知道一點就足夠生成程式碼了。下一篇就可以正式進入程式碼生成部分

另外,歡迎Star這個專案!