⚠️ 本文所使用的 QEMU 版本為:
v4.2.0
延續 Part 1. 一文,本文將繼續介紹 Decodetree 中的 Patterns 及 Pattern Groups 語法。
Patterns
Pattern 實際定義了一個指令的 decode 方式。Decodetree 會根據 Patterns 的定義,來動態產生出對應的 switch-case decode 判斷式。
其語法由使用者所定義的 identifier,隨後緊接著一個以上的 pat_elt。
identifier可由開發者自訂,如:addl_r、addli… 等。pat_elt則可以採用以下不同的語法:fixedbit_elt與在Format中fixedbit_elt的定義相同。field_elt與在Format中field_elt的定義相同。field_ref與在Format中field_ref的定義相同。args_ref與在Format中args_ref的定義相同。fmt_ref直接參考一個被定義過的 Format。const_elt可以直接指定某一個argument的值。
由於 Pattern 實際定義了一個指令的 decode 方式,因此所有的 bits 及 arguments (如果有參考 args_ref 的話) 都必須明確的被定義,如果在搭配了所有的 pat_elt 後還有未定義的 bits 或是 arguments 的話,Decodetree 便會報錯。
此外,Pattern 所產生出來的 decoder,最後還會呼叫對應的 translator function。
translator function需開發者自行定義。
Examples
| |
定義了 addl_i 這個指令的 Pattern,其中:
- insn[31:26] 為
010000。 - insn[11:5] 為
0000000。 - 參考了 Part 1. Examples 定義的
@opiFormat。 - 由於
Pattern的所有 bits 都必須明確的被定義,因此@opi必須包含其餘insn[25:12]及insn[4:0]的格式定義,否則Decodetree便會報錯。
最後 addl_i 的 decoder 還會呼叫 trans_addl_i() 這個 translator function。
搭配之前介紹的 Fields、Argument Sets 及 Formats,讓我們再看幾個完整的例子應該會更清楚 Decodetree 是怎產生一個指令的 decoder 的。
首先是 RISC-V 的 lui 及 auipc 指令:

| |
會產生以下 lui 及 auipc 的 decoder:
| |
回顧到目前為止所介紹的:
Argument Sets:&u這個argument set包含了imm及rd這兩個arguments。Fields:imm及rd分別位在 insn[31:12] 及 insn[11:7],且imm為sign-extended。最後在擷取出imm的值後,還會呼叫ex_shift_12()。Formats:@u定義了 RISC-VU-type指令的格式- 參考了
&u這個Argument Set,因此 decode function 會傳入arg_u作為參數。 - insn[31:12] 參考了
imm_u這個Field(並重新命名為imm) - insn[11:7] 參考了
rd這個Field。
- 參考了
Patterns:lui的opcode(insn[6:0]) 為0010111,也就是0x17,在產生出來的switch-case中可以看到其對應的case。lui的 decoder 最後呼叫了trans_lui(),並傳入DisasContext及經由decode_insn32_extract_u()所解析出來的arg_u。auipc的opcode(insn[6:0]) 為0110111,也就是0x37,在產生出來的switch-case中可以看到其對應的case。auipc的 decoder 最後呼叫了trans_auipc(),並傳入DisasContext及經由decode_insn32_extract_u()所解析出來的arg_u。- P.S. 這邊由於
Decodetree發現lui及auipc可以共用decode_insn32_extract_u(),因此將其提到了switch-case之外。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21static bool decode_insn32(DisasContext *ctx, uint32_t insn) { union { arg_u f_u; } u; decode_insn32_extract_u(ctx, &u.f_u, insn); switch (insn & 0x0000007f) { case 0x00000017: /* ........ ........ ........ .0010111 */ /* ./insn32.decode:18 */ if (trans_auipc(ctx, &u.f_u)) return true; return false; case 0x00000037: /* ........ ........ ........ .0110111 */ /* ./insn32.decode:17 */ if (trans_lui(ctx, &u.f_u)) return true; return false; } return false; }我們另外可以發現,
Pattern+Format把所有的 32-bits 都給了明確的定義:Pattern定義了opcode(insn[6:0])。Format參考了imm(insn[31:12]) 及rd(insn[11:7])。
如果有任何未明確定義的 bits 的話,
Decodetree便會報錯,例如如果我們將lui的opcode最高 2 個 bits (insn[6:5]) 由01改成..:1lui .................... ..... ..10111 @uDecodetree在解析時,便會報錯:./insn32.decode:17: error: (‘bits left unspecified (0x00000060)’,)
Decodetree提醒我們,insn[6:5] (0x00000060) 尚未給出明確定義,並會顯示出其錯誤的行數。trans_lui()和trans_auipc()被定義在target/riscv/insn_trans/trans_rvi.inc.c:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15static bool trans_lui(DisasContext *ctx, arg_lui *a) { if (a->rd != 0) { tcg_gen_movi_tl(cpu_gpr[a->rd], a->imm); } return true; } static bool trans_auipc(DisasContext *ctx, arg_auipc *a) { if (a->rd != 0) { tcg_gen_movi_tl(cpu_gpr[a->rd], a->imm + ctx->base.pc_next); } return true; }可以看到
trans_*()負責實際指令的 business logics 及產生對應的TCG codes。
如同先前所介紹,Patterns 的 pat_elt 也可以採用 field_elt 語法,如 RISC-V 的 fence 指令:
| |
- insn[27:24] 為
pred。 - insn[23:20] 為
succ。 - insn[14:12] 固定為
000。 - insn[6:0] 為
opcode(0001111)。 - 沒有參考任何的
Format。 - 剩下的 insn[31:28]、insn[19:15]、insn[11:7] 被宣告為
-,因此就算沒有被明確定義也沒有關係。
所生成 fence 的 decoder 如下:
| |
值得注意的是,雖然這次我們沒有參考任何的 Argument Set,但 Decodetree 還是替我們生成了一個包含 pred 和 succ 的 arg_decode_insn320 。
trans_fence() 同樣是被定義在 ./target/riscv/insn_trans/trans_rvi.inc.c:
Pattern Groups
Pattern Groups 由一個以上的 Patterns 所組成,其主要差別是不同 Patterns 之間的 bits 可以 overlap。當同組中有多個 Patterns 時,會依據該組中各 Pattern 的宣告順序依序判斷目前的指令是否符合其定義。除此之外,當符合的 Pattern 其 trans_*() 回傳值為 false 時,也會被視為不相符,而繼續判斷該組中的下一個 Pattern。因此 Pattern Groups 非常適合將多個相似格式的指令給組成同一個 Pattern Group。
原文說明如下:
Unlike ungrouped patterns, grouped patterns are allowed to overlap. Conflicts are resolved by selecting the patterns in order. If all of the
fixedbitsfor a pattern match, its translate function will be called. If the translate function returnsfalse, then subsequent patterns within the group will be matched.
| |
各 Pattern Group 以 { 開頭,並以 } 結尾,且允許 nested pattern groups 的存在,其他語法皆與 Pattern 相同。
Examples
會產生以下的 decoder:
| |
當指令的值符合 nop 及 copy 這個內層 Pattern Group 時,會先判斷該指令是否符合 nop 指令的定義,且 trans_nop() 的回傳值為 true。否則的話,就會繼續判斷是否符合同組中的 copy 指令。若都不符,就會再判斷是否符合外層 Pattern Group 的 or 指令。若仍不符,才會回傳 false 表示 decode 失敗。
與單純使用 Pattern 最大不同的是,當一 Pattern 的 trans_*() 回傳值為 false 時,不會直接回傳 false (代表 decode 失敗),而是會接續著判斷後續的 Patterns 是否相符。
RISC-V Compressed-Extension 中的 c.ebreak、c.jalr、及 c.add 指令,由於這三個指令的格式非常相似,因此非常適合使用 Pattern Group 來定義:

RISC-V spec. 中定義:
C.EBREAK shares the opcode with the C.ADD instruction, but with rd and rs2 both zero, thus can also use the CR format.
C.JALR is only valid when rs1≠x0; the code point with rs1=x0 corresponds to the C.EBREAK instruction.
C.ADD is only valid when rs2≠x0; the code points with rs2=x0 correspond to the C.JALR and C.EBREAK instructions. The code points with rs2̸=x0 and rd=x0 are HINTs.
c.ebreak、c.jalr、c.add 三個指令:
- insn[15:13]、insn[12]、insn[1:0] 的值皆相同。
- 當 insn[11:7] 且 insn[6:2] 的值皆為
0(rs1=0且rs2=0) 時為c.ebreak指令。 - 當只有 insn[11:7] 的值為
0(rs1=0且rs2≠0) 時為c.jalr指令。 - 否則為
c.add指令 (rs1≠x0且rs2≠0)。
| |
所生成的 decoder 如下:
| |
當指令格式符合 c.ebreak、c.jalr、c.add 的 Pattern Group 時,會依序判斷該指令是否符合 c.ebreak、c.jalr、c.add 的定義以及其對應的 trans_*()。
另外值得一提的是,在 c_jalr Format 和 jalr Pattern 中有分別指定其 imm 及 rd 的值為 0,所生成的 codes 也會分別在對應的地方將該值設為 0 (見 codes 註解說明)。
以上就是 Decodetree 的語法說明。透過 Decodetree,我們不用再像以前以樣寫一大包的 switch-case 來 decode 指令。將不同類型的指令寫至不同的 decode 檔,不僅方便維護,閱讀起來也更為容易。