⚠️ 本文所使用的 QEMU 版本為:
v4.2.0
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 這種具有固定指令格式的 ISA1。
- 因為各欄位都在固定的位置,(如 RISC-V 的
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就有如下的宣告:1 2 3 4 5 6 7 8 9 10 11 12 13... 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 參數)
Decodetree 的語法共分為:Fields、Argument Sets、Formats、Patterns、Pattern Groups 五部分。本文將介紹如何透過 Decodetree 的語法,來動態生成一個指令的 decoder。
Fields
Field 定義如何取出一指令中,各欄位 (eg: rd, rs1, rs2, imm) 的值。
其語法由 % 開頭,隨後緊接著一個 identifier 及零個或多個 unamed_field,並可再加上可選的 !function。
identifier可由開發者自訂,如:rd、imm… 等。unamed_field定義了該欄位的所在位元。第一個數字定義了該欄位的least-significant bit position,第二個數字則定義了該欄位的位元長度。另外可加上可選的s字元來標明在取出該欄位後,是否需要做sign-extended。- Eg:
%rd 7:5代表rd佔了指令中 bits 7 ~ bits 11 的位置 (insn[11:7]),共 5 bits。
- Eg:
!function定義在擷取出該欄位的值後,所會再呼叫的 function。
Field (32-bits 指令) 最後會生成對應的 extract32() 及 sextract32() 程式碼2,以用來取得指令中各欄位的值:
Examples
| Input | Generated code |
|---|---|
| %disp 0:s16 | sextract(i, 0, 16) |
| %imm9 16:6 10:3 | extract(i, 16, 6) « 3 | extract(i, 10, 3) |
| %disp12 0:s1 1:1 2:10 | sextract(i, 0, 1) « 11 | extract(i, 1, 1) « 10 | extract(i, 2, 10) |
| %shimm8 5:s8 13:1 !function=expand_shimm8 | expand_shimm8(sextract(i, 5, 8) « 1 | extract(i, 13, 1)) |
以 RISC-V 的 U-type 指令為例:

其中,imm 佔 insn[31:12],rd 佔 insn[11:7],且 imm 需要做 sign-extended 後 左移 12 位 (20-bit immediate is shifted left by 12 bits to form U immediates)。因此,如果我們要定義 RISC-V 的 U-type 指令,則可以宣告成:
最後會生成如下的程式碼:
(P.S. static void decode_insn32_extract_u() 是由 Format 定義所生成的,而 arg_u *a 則是由 Argument Set 定義所生成的,將會在後面的部分再做說明)
可以看到:
a->imm是由insn[31:12]所取得並做sign-extended,且會再呼叫ex_shift_12()來左移 12 個 bits。- P.S. RISC-V 的
ex_shift_12()是透過定義在./target/riscv/translate.c中EX_SH這個 macro 所展開的:
- P.S. RISC-V 的
a->rd是由insn[11:7]所取得。
此外,在 Decodetree 的 spec. 中也有提到,我們可以透過只定義 !function 來直接呼叫該 function。在這種情況下,只有 DisasContext 會被傳入該 function。
One may use !function with zero unnamed_fields. This case is called
a parameter, and the named function is only passed the DisasContext
and returns an integral value extracted from there.
如 ARM Thumb ./target/arm/t16.decode 就有定義:
未包含任何 unnamed_fields 或 !function 的 Field 會被視為錯誤。
A field with no unnamed_fields and no !function is in error.
Argument Sets
Argument Set 定義用來保存從指令中所擷取出來各欄位的值。
其語法由 & 開頭,隨後緊接著一個或多個的 identifier ,並可再加上可選的 !extern 。
identifier可由開發者自訂,如:regs、loadstore… 等。!extern則表示是否在其他地方已經由其他的 decoder 定義過。如果加上的話,就不會再次生成對應的argument set struct。
Examples
| |
會生成以下的 argument set struct:
| |
則會生成以下的 argument set struct:
因此,以剛剛的 RISC-V U-type 指令為例,我們需要從指令中擷取 imm 及 rd 欄位的值,可以宣告其 argument set 如下:
| |
最後會生成以下的 argument set struct:
此 argument set struct 會被傳入由 Format 定義所生成的 extract function:
所傳入的arg_u 會保存從指令中擷取出的 imm 及 rd 欄位的值,待後續使用。
Formats
Format 定義了指令的格式 (如 RISC-V 中的 R、I、S、B、U、J-type),並會生成對應的 decode function。
其語法由 @ 開頭,隨後緊接著一個 identifier 及一個以上的 fmt_elt。
identifier可由開發者自訂,如:opr、opi… 等。fmt_elt則可以採用以下不同的語法:fixedbit_elt包含一個或多個0、1、.、-,每一個代表指令中的 1 個 bit。.代表該 bit 可以用0或是1來表示。-代表該 bit 完全被忽略。
field_elt可以用 Field 的語法來宣告。- Eg:
ra:5、rb:5、lit:8
- Eg:
field_ref有下列兩種格式 (以下範例參考上文所定義之 Field):args_ref指定所傳入 decode function 的Argument Set。若沒有指定args_ref的話,Decodetree會根據field_elt或field_ref自動生成一個Argument Set。此外,一個Format最多隻能包含一個args_ref。
當 fixedbit_elt 或 field_ref 被定義時,該 Foramt 的所有的 bits 都必須被定義 (可透過 fixedbit_elt 或 . 來定義各個 bits,空格會被忽略)。
Examples
| |
定義了 op1 這個 Format,其中:
- insn[31:26] 可為
0或1。 - insn[25:21] 為
ra。 - insn[20:13] 為
lit。 - insn[12] 固定為
1。 - insn[11:5] 可為
0或1。 - insn[4:0] 為
rc。
此 Format 會生成以下的 decode function:
由於我們沒有指定 args_ref,因此 Decodetree 根據了 field_elt 的定義,自動生成了 arg_decode_insn320 這個 Argument Set。
以 RISC-V I-type 指令為例:
定義了 i 這個 Format,其中:
- insn[31:20] 為
imm,且為sign-extended。 - insn[19:5] 為
rs1。 - insn[11:7] 為
rd。
此外,我們可以看到:
- 此
Format指定了Argument Set:&i。&i中必須包含所有有用到的arguments(也就是:imm、rs1及rd) imm是透過重新命名的方式來參考%imm_i這個Field。
此範例會生成以下的 decode function:
相比於第一個範例,由於這次我們有指定 args_ref:&i,因此對應的 arg_i 會被傳入 decode function。
回到先前的 RISC-V U-type 指令,我們可以如同 I-type 指令定義其格式:
定義了 u 這個 Format,其中:
- insn[31:12] 為
imm,且為sign-extended。 - insn[11:7] 為
rd。
會生成以下的 decode function:
我們可以看到:
- 此
Format指定了Argument Set:&u。&u中必須包含所有有用到的arguments(也就是:imm、rd) imm是透過重新命名的方式來參考%imm_u這個Field。
以上就是 Decodetree 的 Fields、Argument Sets 及 Formats 語法的簡介。剩下的 Patterns 及 Pattern Groups 就留到 Part 2. 再做介紹。
decodetree 參數
--translate:translator function 的 prefix,預設為trans。一旦指定後,translator function 的 scope 就不會再是static。--decode:decode function 的 prefix,預設為decode,且 scope 為static。一旦指定後,decode function 的 scope 就不會再是static。--static-decode:如同--decode,不過 decode function 的 scope 仍維持為static。-o/--output:指定生成的 decoder.c檔路徑。-w/--insnwidth:指令長度,eg:32or16,預設為32。--varinsnwidth:指令為不定長度。最後一個參數為輸入的 decode 檔路徑。
執行範例:
ARM 其實在
Decodetree引進後,也有部分的 instructions 改採用Decodetree來動態生成對應的 instruction decoders,如 Thumb 指令:./target/arm/t32.decode及./target/arm/t16.decode。 ↩︎extract32()及sextract32()被定義在include/qemu/bitops.h:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18/** * extract32: * @value: the value to extract the bit field from * @start: the lowest bit in the bit field (numbered from 0) * @length: the length of the bit field * * Extract from the 32 bit input @value the bit field specified by the * @start and @length parameters, and return it. The bit field must * lie entirely within the 32 bit word. It is valid to request that * all 32 bits are returned (ie @length 32 and @start 0). * * Returns: the value of the bit field extracted from the input value. */ static inline uint32_t extract32(uint32_t value, int start, int length) { assert(start >= 0 && length > 0 && length <= 32 - start); return (value >> start) & (~0U >> (32 - length)); }↩︎1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24/** * sextract32: * @value: the value to extract the bit field from * @start: the lowest bit in the bit field (numbered from 0) * @length: the length of the bit field * * Extract from the 32 bit input @value the bit field specified by the * @start and @length parameters, and return it, sign extended to * an int32_t (ie with the most significant bit of the field propagated * to all the upper bits of the return value). The bit field must lie * entirely within the 32 bit word. It is valid to request that * all 32 bits are returned (ie @length 32 and @start 0). * * Returns: the sign extended value of the bit field extracted from the * input value. */ static inline int32_t sextract32(uint32_t value, int start, int length) { assert(start >= 0 && length > 0 && length <= 32 - start); /* Note that this implementation relies on right shift of signed * integers being an arithmetic shift. */ return ((int32_t)(value << (32 - length - start))) >> (32 - length); }