owned this note
owned this note
Published
Linked with GitHub
---
tags: rv32emu
---
# rv32emu 改進方案
# issues
# 過多 block
每個 block 紀錄到的指令數量有多有少,執行頻率也不一樣,最遭的情況是指令多的執行1次,指令少的執行很多次。
解法: 合併 block
以 c2mir 的 API 來看只有兩種做法,合併 function body 或是合併 insn 再重新 codegen。
MIR 的文件關於 module 有提到 MIR_new_import 的函式,我看不懂怎麼用,因為參數只要丟 ctx 跟字串就好了,但是我追蹤 MIR_link 使用 import_resolver 時會用到這個函式。
從 MIR 輸出的 IR 可以看到 import_solver 引入的函式(假設是 printf)會變成 `import printf`,我不確定 MIR 的編譯器會不會去優化 printf 還是單純 import,看起來後者比較有可能發生。
# MIR module 沒有清理
* 以 rv32emu 的現況來看,編譯出 JIT 函式就可以刪掉,因為 module 之後不會用到
* ravi 沒看到有刪除 module 的操作,但是會去用 [GC 回收編譯後的程式碼](https://the-ravi-programming-language.readthedocs.io/en/latest/ravi-jit-status.html#the-architecture-of-ravi-s-jit-compilation)
> The compiled code is garbage collected as normal by Lua
---
# Control flow graph 紀錄 Hot path
> 有學過 graph,不過目前還不會寫 CFG 但是想到怎麼用 CFG 來 codegen 跟處理 branch 的方式。
使用 CFG 紀錄 hot path 直到超過預期的數量或是回到迴圈的開頭,也可以像 ravi 設定一個指令數量的 range 來判斷是否要 codegen,這能夠比過往單存紀錄 branch, jal/jalr 還要多的指令,也可以改善目前執行 block by block 遇到的問題。
步驟如下
* 建立 CFG 並開始紀錄指令,重複的指令就不要放進 CFG,如此便能夠紀錄迴圈而不紀錄重複的 insn 到 CFG。
* 走訪 CFG 來 codegen
* 編譯 codegen 的 C 程式碼
* 快取 JIT 函式
## 分支 codegen 的處理方式
分支的 codegen 用 next 紀錄跳躍的地址也就能夠檢查是否要跳躍,再去生成 true 跟 false 的對應程式碼。
先把 true 的 blocks 都生成完才生成 `} else {` 跟之後的 blocks,因此 traversal 的方式很重要。
```c
void emit_branch(rv, insn) {
bool taken = false;
// ....
uint32_t next = 0;
if(taken) {
rv->PC += imm;
next = pc;
} else {
rv->PC += 4;
}
}
if(rv->PC == next) {
// true
} else {
// false
}
```
以下圖的 CFG 為例,其中 A 跟 B 有分支,true 用紅色表示,false 則用藍色,黑色表示下一個
```graphviz
digraph
{
rankdir="TD";
block_a[label="A",shape="circle", style="bold, filled", fillcolor="#ffffff"]
block_b[label="B",shape="circle", style="bold, filled", fillcolor="#ffffff"]
block_c[label="C",shape="circle", style="bold, filled", fillcolor="#ffffff"]
block_d[label="D",shape="circle", style="bold, filled", fillcolor="#ffffff"]
node [ shape="circle", style="bold, filled", fillcolor="#dddddd",fixedsize=true,width=0.53 ];
block_a -> block_b[color=red]
block_a -> block_c[color=blue]
block_b -> block_c[color=red]
block_b -> block_d[color=blue]
block_c -> block_b
block_c -> block_d[style=invis]
}
```
預期生成的 C code 大致會像下面這樣,為了避免重複生成一樣的 block 可以用 goto 來處理
```c
[block A]
if(rv->PC == next_A) {
block_B:
[block B]
} else {
block_C:
[block C]
goto block_B
}
if(rv->PC == next_B) {
goto block_C;
} else {
[block D]
}
```
* 因為有 goto 跟 if 的緣故,不能保證執行指令就跟紀錄到的指令長度一樣,因此要在 codegen 時寫入更新虛擬機 cycle 的程式碼
* 或許能用 predict 的方式去合併下一塊 block,如下範例,剛開始會先生成 i < 500 的 block 之後才生成 else 區塊的 block,兩者可以做合併
```c
int sum = 0;
for(int i = 0; i < 1000; i++) {
if(i < 500) {
sum += 2;
} else {
sum += 1
}
}
```