---
tags: sysprog2017
---
# 2017q3 Homework3 (simulator)
<contributed by `HexRabbit`>
### 作業沒寫完對不起各位納稅人
## 作業說明
https://hackmd.io/hFGdxT1JRPa8SbdofdNgQw
## 心得筆記
- 讀取記憶體相比數值運算來的慢
- cacheline競爭會大幅降低效能
- <s>多線程</s> 多執行緒可能會有shared cacheline不一致問題 -> cacheline中的資料被修改後,對其他cpu來說會成為無效資料
- 硬體本身可能也會對操作優化
- vm.c 中的do{...}while(0)是做什麼用的? [(使用define的函式時可以加上`;`)](https://stackoverflow.com/questions/9495962/why-use-do-while-0-in-macro-definition)
- __vm_env 與 vm_env 有什麼關係? 找半天原來vm_env是個typedef定義在vm.h (相當不直覺,應該在同一文件定義)
- `#define OP(name) OP_##name` `##`串接前後字串
## 程式架構
driver.c: as_exec的主邏輯設計
opcode.c: 定義各個opcode(除call,ret,helt,jmp類)的實做
vm.c: opcode(call,ret,helt,jmp類)的實做,虛擬機器初始化,暫存器定義
as.c: 語法解析,另存成可運行的結構(存於vm_env內)
elf.c: 產生elf檔案用
--
vm_value: 儲存`char *`或`int`的結構
vm_operand: 儲存運算子
vm_inst: 儲存運算符
vm_seg_info: 資料串列
vm_regs: 儲存pc,sp,from(目前pc在ret後的上一個inst),to(目前branch的第一個inst)
vm_env: 紀錄目前虛擬機狀態
vm_opcode_impl: 儲存opcode實做
vm_handler: function pointer,可用來模擬OOP中的function
## 錯誤修正
```C
static void and_impl(VM_HANDLER_ARGS)
{
- if (VM_T(op1) == INT && VM_T(op1) == INT) {
+ if (VM_T(op1) == INT && VM_T(op2) == INT) {
VM_INT(result) = VM_INT(op1) & VM_INT(op2);
} else {
UNIMPL;
}
}
static void or_impl(VM_HANDLER_ARGS)
{
- if (VM_T(op1) == INT && VM_T(op1) == INT) {
+ if (VM_T(op1) == INT && VM_T(op2) == INT) {
VM_INT(result) = VM_INT(op1) | VM_INT(op2);
} else {
UNIMPL;
}
}
```
[issue](https://github.com/sysprog21/full-stack-hello/pull/3)
if 判斷式中重複檢查了兩次 VM_T(op1) == INT,應將 op1 改為 op2。
```python
f.write('''/* File opcode.h was generated by scripts/gen_opcode.py. */
#ifndef OPCODE_H
#define OPCODE_H
+ #include "vm.h"
/* opcode listing */
enum {\n''')
```
[issue](https://github.com/sysprog21/full-stack-hello/pull/4)
由 python 腳本生成的 opcode.h 使用到外部定義的 type vm_env 而未引入 vm.h 可能導致編譯錯誤,將其引入即可。
:::danger
請提交 pull request,謝謝
:notes: jserv
:::
:::info
搞定!
:notes: HexRabbit
:::
## 功能修改
### 於 call 和 jmp 類實做 $ 及 # 功能
觀察程式碼時發現 JMP 類與 CALL 在呼叫時不會檢查參數的型別是 CONST 還是 TEMP,預設使用者將填入`#X`(TEMP 型別),並直接取出參數(`value.id`)作為 pc 的目標,不僅使得以`$X`呼叫時會出錯外(`value.id` != 在 cpool 中的 index),使用`#X`呼叫卻完全不理會 stack 中存的值,僅僅只是作為一個符號來使用也相當的不符合程式寫作的邏輯。
重新修改為支援`#`從stack取值,以`$`使用常數操作
```C
+#define VM_CALL() \
+ do { \
+ vm_push(env, env->r.pc); \
+ size_t n = vm_get_op_value(env, &OPCODE.op1)->value.vint; \
+ GOTO(n); \
+ } while (0)
+
+#define VM_JMP() \
+ do { \
+ size_t n = vm_get_op_value(env, &OPCODE.op1)->value.vint; \
+ GOTO(n); \
} while (0)
```
### 實做label
修改 make_from_operand()
```C
+ case ':':;
+ int label = vm_find_label(env, &data[1]);
+ if (~label) {
+ op.type = CONST;
+ op.value.id = vm_add_const(env, INT, &label);
+ break;
+ } else {
+ printf("Error: specified label name '%s' not found.\n", data);
+ free(line);
+ exit(-1);
+ break;
+ }
```
修改 assemble_from_fd()
```C
while (getline(&line, &size, fp) != -1) {
if (line[0] == ';' || line[0] == '\n')
continue;
/* Remove trailing newline feed */
line[strcspn(line, "\r\n")] = 0;
+ if (line[strlen(line) - 1] == ':') {
+ line[strlen(line) - 1] = 0;
+ vm_make_label(env, line, insts_count);
+ } else
+ insts_count++;
+ }
+
+ fseek(fp, 0, SEEK_SET);
+
+ while (getline(&line, &size, fp) != -1) {
+ if (line[0] == ';' || line[0] == '\n' || line[strlen(line) - 2] == ':')
+ continue;
+ /* Remove trailing newline feed */
+ line[strcspn(line, "\r\n")] = 0;
assemble_line(env, line);
}
free(line);
```
採用 djb2 作為 hash function,並暫時選擇 49157 作為 hash table size。
## Fibonacci 實做
### iterative
```=0
add $5 $0 #0 ; init to 5
jz #0 $8
add $0 $0 #1
add $1 $0 #2
add #1 #2 #2
sub #2 #1 #1
sub #0 $1 #0
jnz #0 $4
print #1
```
與一般撰寫程式差不多,很直覺。
### recursive
```=0
add $5 $0 #2 ; init to 5
jmp $14
jz #2 $13 ; if 0
sub #2 $1 #3
jz #3 $12 ; if 1
sub #2 $1 #2
call $2
add #2 $1 #2
sub #2 $2 #2
call $2
add #2 $2 #2
ret
add #0 $1 #0 ; add 1 to val
ret
call $2
print #0
```
jmp 到後面然後再 call 回來的作法實在不是很直觀,就算我以前有寫一點 shellcode 的經驗還是花了些思考時間在上面。
使用此方式若數字過大的話會 overflow 到 heap 上。
## 參考資料
1. https://www.lingholic.com/how-many-words-do-i-need-to-know-the-955-rule-in-language-learning-part-2/
2. http://onlinelibrary.wiley.com/doi/10.1111/j.0039-3193.2004.00109.x/pdf