type
status
date
slug
summary
tags
category
icon
password
本文所使用的 QEMU 版本為:
v4.2.0QEMU 在 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 檔。- 因為各欄位都在固定的位置,(如 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就有如下的宣告:
(實際參數說明請見 decodetree.py 參數)
Decodetree 的語法共分為:
Fields、Argument Sets、Formats、Patterns、Pattern Groups 五部分。本文將介紹如何透過 Decodetree 的語法,來動態生成一個指令的 decoder。Fields
Field 定義如何取出一指令中,各欄位 (e.g. rd, rs1, rs2, imm) 的值。其語法由
% 開頭,隨後緊接著一個 identifier 及零個或多個 unamed_field,並可再加上可選的 !function。identifier可由開發者自訂,如:rd、imm... 等。
unamed_field定義了該欄位的所在位元。第一個數字定義了該欄位的least-significant bit position,第二個數字則定義了該欄位的位元長度。另外可加上可選的s字元來標明在取出該欄位後,是否需要做sign-extended。- E.g.
%rd 7:5代表rd佔了指令中 bits 7 ~ bits 11 的位置 (insn[11:7]),共 5 bits。
!function定義在擷取出該欄位的值後,所會再呼叫的 function。
Input | Generated Codes |
%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()是透過定義在translate.c中EX_SH這個 macro 所展開的:
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 就有定義:
未包含任何
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的語法來宣告。- E.g.
ra:5、rb:5、lit:8 field_ref有下列兩種格式 (以下範例參考上文所定義之Field):'%' identifier:直接參考一個被定義過的Field。- 如:
%rd,會生成: identifier '=' '%' identifier:直接參考一個被定義過的Field,但透過第一個identifier來重新命名其所對應的argument名稱。此方式可以用來指定不同的argument名稱來參考至同一個Field。- 如:
my_rd=%rd,會生成: 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
定義了
opi 這個 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.py 參數
—-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 檔路徑。
呼叫範例:
附註
- 註1:ARM 其實在 Decodetree 引進後,也有部分的 instructions 改採用 Decodetree 來動態生成對應的 instruction decoders
- 如 Thumb 指令:
target/arm/t32.decode及target/arm/t16.decode。
- 註2:
extract32()及sextract32()被定義在include/qemu/bitops.h: