# 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 :::