contributed by <HMKRL
>
由 driver.c
開始觀察,得知 vm 啟動的程式碼如下:
case LOAD_ELF_AND_EVAL: {
vm_env *env = vm_new();
load_from_elf(env, in_fd);
hook_opcodes(env);
vm_run(env);
vm_free(env);
break;
}
vm_env 的結構如下
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;
};
function call stack 位於 temps
其中 register 部份
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;
vm_run
的部份大量使用 macro, 因此採用 gcc -E
觀察展開後的 vm_run
const static void *labels[] = {&&OP_HALT, &&OP_ADD, &&OP_SUB, &&OP_PRINT, &&OP_JLT, &&OP_JLE, &&OP_JZ, &&OP_JGE, &&OP_JGT, &&OP_JNZ, &&OP_JMP, &&OP_MUL, &&OP_DIV, &&OP_MOD, &&OP_CALL, &&OP_RET, &&OP_AND, &&OP_OR, &&OP_NOT, &&OP_XOR, &&OP_LSL, &&OP_LSR, &&OP_ASR,}; \
goto *labels[env->insts[env->r.pc].opcode];
此處採用了 gcc computed goto 跳至 pc 所指向的 instruction, 以避免 switch-case 造成的 branch:
參考此處,其中解釋 computed goto 細節:
default
需要),因此可以減少指令數目說話不精確,改用 computed goto 仍有 branch,請詳細描述具體落差,以及用計算機結構的觀點闡述
typedef struct {
int opcode;
vm_operand op1;
vm_operand op2;
int result;
} vm_inst;
每個 instruction 會透過以下方法執行:
OP_ADD : do {
if (env->impl[env->insts[env->r.pc].opcode].handler)
env->impl[env->insts[env->r.pc].opcode].handler(
vm_get_op_value(env, &env->insts[env->r.pc].op1),
vm_get_op_value(env, &env->insts[env->r.pc].op2),
vm_get_temp_value(env, env->insts[env->r.pc].result)
);
do {
++env->r.pc;
goto *labels[env->insts[env->r.pc].opcode];
} while (0);
} while (0);
TO HTYISABUG: do{} while(0);
是為了避免單行 if 沒加大括弧造成巨集不完整的手法(參考此處)
術語叫做 dangling else
參考 tests/mul.s
mul $3 $2 #1 ; multiply: (3)*(2)
print #1
mul $3 $-2 #2 ; multiply: (3)*(-2)
print #2
可知 $X
代表常數,#X
則是 register
可用的 register 上限則是 CPOOL_MAX_SIZE