{{ $root.errMsg }}

【深入淺出教你寫編譯器(Compiler)】五、虛擬機(Virtual Machine)(下)

(已刪除)

(已關閉)

(已標記為濫發)

(已保護)


現在開始要編寫執行功能,執行功能很簡單,只是把每一句指令都執行一次就可以了。

emachine.prototype.run = function (){
    for (var i = 0, l = this.instructions.length; i < l; i++){
        var instruction = this.instructions[i];
        this[instruction.opcode].apply(this, instruction.operands);
    }
}

最後就是要編寫指令的實際功能了,lwi 要做的就是直接把一個數值寫到 register 中,所以我們只需 call 一下 setRegisterContent 就可以了,而 print 要做的就是把一個 register 中的數值寫到 output 去,所以就有以下的代碼了:

Wemachine.prototype.lwi = function (operand1, operand2){
    this.setRegisterContent(operand1, parseInt(operand2));
}
 
Wemachine.prototype.print = function (operand1){
    var val = this.getRegisterContent(operand1);
    log(val);
}

放在一起,我們運行一下程式,看看結果。

var a:int = 3 / 5;
		a= a++ + a-- - ++a + --a;
		
		var b:bool = ! true && ! false || (1 + 3 == -3);
		b+=3;
		
		while (1 == 3){
		}
		
		if (b + 1){
		}
		
		
lwi $0,100;
print $0;
		

100

很好,現在就把餘下的 Data transfer 功能都編寫下來吧。

Wemachine.prototype.move = function (operand1, operand2){
    this.setRegisterContent(operand1, this.getRegisterContent(operand2));
}
Wemachine.prototype.lwi = function (operand1, operand2){
    this.setRegisterContent(operand1, parseInt(operand2));
}
Wemachine.prototype.lui = function (operand1, operand2){
    this.setRegisterContent(operand1, parseInt(operand2) << 16);
}

很直觀,沒什麼特別之處,這裏就不多解釋了。Arithmetic 的處理其實都很直觀,唯一需要提醒讀者的地方是,做除數運算是有可能會出現 Division by zero 的情況,所以我們要發出 runtime error。

var a:int = 3 / 5;
		a= a++ + a-- - ++a + --a;
		
		var b:bool = ! true && ! false || (1 + 3 == -3);
		b+=3;
		
		while (1 == 3){
		}
		
		if (b + 1){
		}
		
		
lwi $0, 100;
lwi $1, 50;
add $2, $0, $1;
print $2;
sub $2, $0, $1;
print $2;
mult $2, $0, $1;
print $2;
div $2, $0, $1;
print $2;
mod $2, $0, $1;
print $2;
addi $2, $0, 20;
print $2;
subi $2, $0, 20;
print $2;
multi $2, $0, 20;
print $2;
divi $2, $0, 20;
print $2;
modi $2, $0, 20;
print $2;
modi $2, $0, 0;
		

Line 0: (Runtime error) Division by zero
150
50
5000
2
0
120
80
2000
5
0

Logical 跟 Arithmetic 的處理方法很相似,這裏就不著墨了。現在到最後要寫 branch 和 jump 了,要實現這個功能,我們要改變一下程式執行的方法,記得我們的 run method 嗎?我們的 run method 是用 i 來做 loop counter 的,現在要改變一下了,要用 program counter 取代 i,這樣我們才可以在其他方法中改變運行次序。

在 label() 中,我們要把想定義的 label 和 program counter 的數值放到 map 裏,這樣我們才可以在後面的程式設定要跳到哪一個位置。現在看看我們的 bne:

Wemachine.prototype.bne = function (operand1, operand2, operand3){
    var nextPC = this.labelMap[operand3];
 
    if (nextPC == null){
        Errors.push({
            type: Errors.RUNTIME_ERROR,
            msg: "Label not found",
            line: 0
        });
    }else{
        var val1 = this.getRegisterContent(operand1);
        var val2 = this.getRegisterContent(operand2);
        if (val1 != val2){
            this.pc = nextPC;
        }
    }
}

首先要在 map 中找找有沒有相關的 label,沒有的話就要發出錯誤,有的話就要看看條件是否成立,是的話就要把 program counter 設定為要跳到的位置,這樣程式在下次 loop 的時候才可以跳到 label 那處。現在看看程式運行結果吧!

var a:int = 3 / 5;
		a= a++ + a-- - ++a + --a;
		
		var b:bool = ! true && ! false || (1 + 3 == -3);
		b+=3;
		
		while (1 == 3){
		}
		
		if (b + 1){
		}
		
		
lwi $0, 5;
lwi $1, 0;
label lbl1;
print $0;
subi $0, $0, 1;
bne $0, $1, lbl1;
j lbl2;
		

Line 0: (Runtime error) Label not found
5
4
3
2
1

現在把剩下來的 branch 都寫下來。

var a:int = 3 / 5;
		a= a++ + a-- - ++a + --a;
		
		var b:bool = ! true && ! false || (1 + 3 == -3);
		b+=3;
		
		while (1 == 3){
		}
		
		if (b + 1){
		}
		
		
lwi $0, 5;
lwi $1, 0;
label lbl1;
print $0;
subi $0, $0, 1;
ble $1, $0, lbl1;
		

5
4
3
2
1
0

大功告成,怎麼樣,如西杰之前所說,這部份不是很難吧,如果大家曾經學過 assembly language 的話應該更容易上手!現在有了這個簡單的虛擬機,下一步我們就可以把之前建立的 parse tree 變成可以在這個 Wemachine 運行的代碼了,下個星期再見吧。

compiler  


{{ ctrl.votes | shortNumber: 0 }}
寫於 {{ '2017-08-14T09:16:38.290Z' | calendarTime }}

{{ $root.errMsg }}