<aside>
⚠️ 本文所使用的 QEMU 版本為:v4.2.0
</aside>
QEMU 在 decode 指令的時候,需要呼叫各平台所定義的 instruction decoders 來解析指令。如在 ARM 平台下,就定義了:disas_arm_insn()、disas_thumb_insn() 及 disas_thumb2_insn() 等來分別負責 ARM 32-bits 指令、ARM Thumb 指令及 ARM Thumb2 指令的解析。
而 Decodetree 則是由 Bastian Koppelmann 於 2017 年在 porting RISC-V QEMU 的時候所提出來的機制 (詳見:討論串 1、討論串 2)。主因是過往的 instruction decoders (如:ARM) 都是採用一大包的 switch-case 來做判斷。不僅難閱讀,也難以維護。
因此 Bastian Koppelmann 就提出了 Decodetree 的機制,開發者只需要透過 Decodetree 的語法定義各個指令的格式,便可交由 Decodetree 來動態生成對應包含 switch-case 的 instruction decoder .c 檔。
Decodetree 特別適合像 RISC-V 這種具有固定指令格式的 ISA [註1[]](https://www.notion.so/Decodetree-5eb0cb1f35ca4794bf3dfb8050cec81c#d9b5f36cf29a497aa764c3d0797085b9)。
opcode 都是固定在 bits[6..0] 的位置),各指令可重複使用的定義相較於其他的 ISA 來得多。
Decodetree 其實是由 Python script (scripts/decodetree.py) 所撰寫的。其規格說明文件可以參考:docs/devel/decodetree.rst,裡面有詳細定義了其語法的格式。QEMU 在編譯時,會呼叫 Decodetree,根據各平台所定義的 decode 檔,動態生成對應的 decoder。
如 RISC-V 的 instruction decoders 就是被定義在:target/riscv/*.decode 中。其 Makefile.obj 就有如下的宣告:
...
DECODETREE = $(SRC_PATH)/scripts/decodetree.py
decode32-y = $(SRC_PATH)/target/riscv/insn32.decode
decode32-$(TARGET_RISCV64) += $(SRC_PATH)/target/riscv/insn32-64.decode
...
target/riscv/decode_insn32.inc.c: $(decode32-y) $(DECODETREE)
$(call quiet-command, \\
$(PYTHON) $(DECODETREE) -o $@ --static-decode decode_insn32 \\
$(decode32-y), "GEN", $(TARGET_DIR)$@)
(實際參數說明請見 decodetree.py 參數)
Decodetree 的語法共分為:Fields、Argument Sets、Formats、Patterns、Pattern Groups 五部分。本文將介紹如何透過 Decodetree 的語法,來動態生成一個指令的 decoder。
Field 定義如何取出一指令中,各欄位 (e.g. rd, rs1, rs2, imm) 的值。
field_def := '%' identifier ( unnamed_field )* ( !function=identifier )?
unnamed_field := number ':' ( 's' ) number
其語法由 % 開頭,隨後緊接著一個 identifier 及零個或多個 unamed_field,並可再加上可選的 !function。
identifier 可由開發者自訂,如:rd、imm... 等。unamed_field 定義了該欄位的所在位元。第一個數字定義了該欄位的 least-significant bit position,第二個數字則定義了該欄位的位元長度。另外可加上可選的 s 字元來標明在取出該欄位後,是否需要做 sign-extended。
%rd 7:5 代表 rd 佔了指令中 bits 7 ~ bits 11 的位置 (insn[11:7]),共 5 bits。!function 定義在擷取出該欄位的值後,所會再呼叫的 function。