type
status
date
slug
summary
tags
category
icon
password
本文所使用的 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
檔。- 因為各欄位都在固定的位置,(如 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:32
or16
,預設為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
: