# 2017q3 Homework3 (simulator)
###### tags: `sysprog2017`
contributed by <`amikai`>
[Github](https://github.com/amikai/full-stack-hello)
# 程式碼研讀
一個 compilation system 通常會提供 compiler driver 幫你方便使用 preprocessor, compiler, assembler, linker, 我們在 linux 下所用的 gcc 指令就是 compiler driver. 此作業裡的 driver 也像是一個 compiler driver 的角色, 當使用 as_exec 這個執行檔寫入 elf 檔案或是執行組合語言都需要透過 driver 將虛擬機器開啟 (vm.c) 依據你的需求輸入不同的選項進行寫入 elf 或是執行.
## 加入 nop 指令
一開始只想說為了要瞭解一下這支程式, 那就加一個指令試試看好了, 但是要怎麼加呢? 翻翻找找看到一個 [PR](https://github.com/jserv/full-stack-hello/pull/43), 這個 PR 是加入 bitwise 指令, 所以我就嘗試加入了一個簡單的指令 nop ([commit](https://github.com/amikai/full-stack-hello/commit/c799a4d301098c0af4681590ddb99fdd7e4c5c08)), 先在 `opcode` 定義 nop 指令, nop 指令是第 23 個指令, 不需要 operand 也不需要 result, 所以這樣填入即可:
```
nop, 23, 0, 0, 0
```
在 vm.c 裡定義 nop 指令, 因爲不用做事所以直接跳下一條指令就對了:
```c
#define VM_NOP() \
do { \
DISPATCH; \
} while (0)
```
然後在 vm_run 加入 nop 指令
```diff
void vm_run(vm_env *env)
{
BEGIN_OPCODES;
OP(ADD) : VM_CALL_HANDLER();
OP(SUB) : VM_CALL_HANDLER();
...
OP(JMP) : GOTO(OPCODE.op1.value.id);
OP(CALL) : VM_CALL(OPCODE.op1.value.id);
OP(RET) : VM_RET();
+ OP(NOP) : VM_NOP();
OP(HALT) : goto terminate;
END_OPCODES;
terminate:
return;
}
```
opcode.h 就不用擔心了 codegen 會自動產生, 所以其實加入指令是非常簡單地
## 加入 label 功能 ~~看到大神同學還沒做 趕緊來做~~
原本在 as.c 檔案裡加入很多程式碼為了要支持 label 功能, 寫完的時候發現編譯一直過不了, 最後發現 vm.h 裡的 forward declaration (typedef struct __vm_env vm_env;) 眼淚都快掉出來了, 寫到最後才發現 vm_env 這個 struct 已經被封裝起來了
> 要加入 label 這個功能主要有兩個功能區塊:
> 1. 給定某一行程式碼一個名稱的 label
> 2. 被當作 operand 的 label (ex: gnz #1 label)
我先從第 1 個功能區塊探討起, 要判斷是不是一個 label 相當簡單, 只要判斷最後字元是不是 `:` 就行了:
```
static bool isLabel(const char *name){
int len = strlen(name);
if(len > 1 && name[len -1] == ':')
return true;
return false;
}
```
接下來延伸的問題就很多了:
我們需要一個新的資料結構紀錄 label, 此資料結構需要包含 label 的名稱以及對應的 instruction 之 pc 值:
``` c
//vm.h
typedef struct {
char *name;
int next_pc;
} vm_label;
```
再來是虛擬機器, 因為第一個功能區塊的 label 並不是指令, 我們需要用額外的空間把 label 記起來所以用 `vm_label labels[LABEL_MAX_SIZE]` 紀錄所有指令, 並且 `int labels_count` 紀錄有指令總數, 至於第二個功能區塊因為跳躍指令若使用 label 作為跳躍, 則要參照到這個 label 拿到它的資訊, 所以用 `vm_operand *labels_ref[INSTS_MAX_SIZE]` 紀錄所有參照到的 label , 並且用 `int labels_refcnt` 紀錄參照 label 的總數
```c
struct __vm_env {
vm_inst insts[INSTS_MAX_SIZE]; /* Program instructions */
vm_label labels[LABEL_MAX_SIZE];
vm_operand *labels_ref[INSTS_MAX_SIZE];
vm_value cpool[CPOOL_MAX_SIZE]; /* Constant pool */
vm_value temps[TEMPS_MAX_SIZE]; /* Temporary storage */
vm_opcode_impl impl[OPCODE_IMPL_MAX_SIZE]; /* OPCODE impl */
vm_regs r;
int insts_count;
int cpool_count;
int temps_count;
int labels_count;
int labels_refcnt;
};
```
觀察 [Add label support](https://github.com/jserv/full-stack-hello/pull/30) 之後發現本來在判斷 operand 程式裡到若不是遇到 `$` `#` 或是字串的以外情況都是當作 label (switch 的 default), 程式錯誤也有可能跳至 default, 所以需要參照到 label 時, 我做了一個 `@` 辨識, 所以跳躍時會像這樣 `jmp @label`
```c
static inline vm_operand make_operand(vm_env *env, char *line, const char *data)
{
...
switch(data[0]){
...
case '@':
op.type = LABEL;
op.label = strdup(data+1);
break;
...
}
}
````
:::danger
如果不依賴 `@` 識別 label 的話,該怎麼做?
:notes: jserv
:::