# 2017q3 Homework3 (simulator) contributed by <`zhanyangch`> ## 分析 [full-stack-hello](https://github.com/sysprog21/full-stack-hello) 程式架構 driver 中負責讀取使用者的輸入,並呼叫對應的函式 * as.c 將虛擬機器的組合語言組譯成機器碼,並且提供寫入、讀取 elf 檔案 * elf.c 處理 elf 所需要的 header * opcode.c 實做虛擬機器指令集(不包含 j type) * vm.c 實做虛擬機器環境、j type 指令 ### 觀察虛擬機器執行 driver.c 可以接受不同的參數,先不看跟 elf 有關的部份 case ASSEMBLE_AND_EVAL ```clike case ASSEMBLE_AND_EVAL: { vm_env *env = vm_new(); assemble_from_fd(env, in_fd); hook_opcodes(env); vm_run(env); vm_free(env); break; } ``` * 首先先建立虛擬機器的環境 ```clike struct __vm_env { vm_inst insts[INSTS_MAX_SIZE]; /* Program instructions */ 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; }; ``` * 觀察 vm_regs 可以發現此 sturct 主要紀錄程式的狀態,而一般運算的 register 則是在 temps 內。 ```clike typedef struct { size_t pc; // program counter. size_t sp; // stack runs from the end of 'temps' region. size_t from; // the immediate PC before last branch/return. size_t to; // the immediate PC after last branch/return. } vm_regs; ``` * assemble_from_fd 此函式將指令的 operand 建立對應的 vm_inst 放入 env 的 insts 中,遇到常數時會呼叫 vm_add_const 將常數加入 env 的 cpool。 * hook_opcodes:將 opcode 的實做放入 env 的 impl * vm_run:包含每個 opcode 對應的 label 利用 gcc 提供的 [Labels-as-Values](https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html)可以使用 `&&OP_CODE` 得到 label address,放入 dispatch table 中,在每一個 instruction 的結尾執行 DISPATCH ,此方式稱為 direct threaded code * vm_free 釋放記憶體空間 ### 指令形式 * 算術運算 - [ ] `add $40 $2 #1` `$`: 常數 `#`: 暫存器(temps) * conditional jump - [ ] `jz #1 #14` `#1`: 暫存器(temps) `#14`: 條件成立時跳到第14個指令 ## 接受 command line 參數 [參考st9007a的共筆](https://hackmd.io/s/Hkf6pkPAb) 接受"--input <value>"的參數將 #0 初始化成<value> 首先在 driver.c 中 加入讀取 --input,在這裡使用 strtol 而非 atoi,原因是 atoi 對於無法處理的字串會回傳 0,但無法區別 0 是因為輸入錯誤或是數字 0而產生,因此無法偵測到錯誤,使用 strtol *pEnd 會被設為讀取完數字後的下一個字元,在這裡 pEnd 若非 '\0',則代表沒有成功讀取數字 ```clike else if (!strcmp(argv[i], "--input")) { char *pEnd; if (!argv[i + 1]) FATAL(-1, "--input need a integer as argument, see -h\n"); temp_init_val = strtol(argv[++i], &pEnd, 10); if (*pEnd) FATAL(-1, "--input need a integer as argument, see -h\n"); } ``` >> 依據給定的 coding style,用 4-spaces 來排版上述程式碼 >> [name="jserv"][color=red] 在 vm.[ch] 中加入 vm_set_temp_value ```clike inline void vm_set_temp_value(vm_env *env, int id, int val) { env->temps[id].type = INT; env->temps[id].value.vint = val; } ``` driver.c 中呼叫 vm_set_temp_value ```clike case ASSEMBLE_AND_EVAL: { vm_env *env = vm_new(); vm_set_temp_value(env, 0, temp_init_val); assemble_from_fd(env, in_fd); hook_opcodes(env); vm_run(env); vm_free(env); break; } ``` ## 實作 Fibonacci 數列 ### iterative 目前採用 int 儲存計算結果,因此當輸入超過 46 時會產生 integer overflow :::danger 需要指出目前採用的 [LP64](https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models) 資料模型下,`int` 實質為 32-bit,而 fib(46) 尚在 32-bit 能表示的數值範圍內。 另外,除了給予輸入數值範圍的提示,也需要提出後續數值表示法的調整方案。 :notes: jserv ::: ``` jz #0 #11 jlt #0 #16 ;check #0 >= 0 sub $46 #0 #4 jlt #4 #16 ;check #0 <= 46 sub #0 $1 #0 or $0 $0 #1; #1=0 or $1 $0 #2; #2=1 ;for add #1 #2 #3 ; fib = # 1+ #2 or #2 $0 #1 ;#1 = #2 or #3 $0 #2 ;#2 = fib sub #0 $1 #0 jgt #0 #7 print #3 halt ;if #0 = 0 , print 0 print #0 halt ;limit n:0~46 print "Please use 0~46 as an input value." halt ``` ### Recursive ``` jz #0 #10 jlt #0 #11 ;check #0 >= 0 sub $46 #0 #4 jlt #4 #11 ;check #0 <= 46 or $0 $0 #3 ;#3 for ans call #12 ;call f(n) print #3 halt ;if #0 = 0, print 0 print #0 halt ;limit n:0~46 print "Please use 0~46 as an input value." halt xor #0 $1 #5 ;check #0 == 1 jz #5 #15 ;if true, do #3+=1 jmp #17 ;return add #3 $1 #3 ret jz #0 #19 ;if true, return jmp #20 ;call f(n-1), f(n-2) ret sub #0 $1 #0 call #12 sub #0 $1 #0 call #12 add #0 $2 #0 ret ``` ## 新增 label 的支援 * 觀察並修改 [Add label support](https://github.com/jserv/full-stack-hello/pull/30) 原本的跳躍指令只能接受跳到某個絕對位置,但在寫程式時,若要新增或刪除指令,則需要將跳躍指令的參數做大量修改,藉由新增對 label 的支援,可以由組譯器幫忙計算跳躍的位置。 組譯的實做方法如下: 1. 當遇到 jmp :<label> 時新增vm_operand * label_reference,使用指標紀錄該 operand 的位置、label 的名稱 - [ ] `as.c` ```clike static inline vm_operand make_operand(vm_env *env, char *line, const char *data) { /*...*/ case ':': op.type = LABEL; op.label = rm_comment_strdup(data + 1); break; /*...*/ } ``` - [ ] `vm.c` ```clike size_t vm_add_inst(vm_env *env, vm_inst inst) { env->insts[env->insts_count] = inst; if (inst.op1.type == LABEL) vm_add_label_ref(env,&env->insts[env->insts_count].op1); if (inst.op2.type == LABEL) vm_add_label_ref(env,&env->insts[env->insts_count].op2); return env->insts_count++; } ``` 2. 當遇到 <label>: 時新增 vm_label labels,紀錄 label 的名稱、當下的 pc - [ ] `vm.c` ```clike size_t vm_add_label(vm_env *env, char *label) { if (vm_find_label(env, label) != -1) FATALX(1, "Label '%s' redefinition\n", label); env->labels[env->labels_count].label = strdup(label); env->labels[env->labels_count].next_pc = env->insts_count; return env->labels_count++; } ``` :::danger 修正上述訊息的文法錯誤 :notes: jserv ::: 3. 當讀取完整份檔案後,將每一個 label_reference 找到對應的 vm_label,將其 value.id 改為 vm_label 內紀錄的 pc 值 - [ ] `vm.c` ``` void vm_register_label(vm_env *env) { for (int i = 0; i < env->label_refcnt; ++i) { env->label_ref[i]->value.id = vm_find_label(env, env->label_ref[i]->label); if(env->label_ref[i]->value.id == -1) FATALX(1,"Label Register Error: Label \"%s\" not found\n", env->label_ref[i]->label); } } ```