LLVMバックエンドを追加するにあたり、MYRISCVXアーキテクチャを定義するためのtdファイルを作成する必要がある。 tdファイルはLLVMのバックエンドを定義するためのDSLで、バックエンドを理解するためには避けては通れないものだ。
命令を定義する
MYRISCVXInstrInfo.td
には先ほど作成したMYRISCVXInstrFormats.td
をincludeする。
//===----------------------------------------------------------------------===// // Instruction format superclass //===----------------------------------------------------------------------===// include "MYRISCVXInstrFormats.td"
//===----------------------------------------------------------------------===// // MYRISCVX Operand, Complex Patterns and Transformations Definitions. //===----------------------------------------------------------------------===// // Instruction operand types // Signed Operand def simm12 : Operand<i32> { let DecoderMethod= "DecodeSimm12"; }
def immSExt12 : PatLeaf<(imm), [{ return isInt<12>(N->getSExtValue()); }]>;
// Arithmetic and logical instructions with 2 register operands. class ArithLogicR<bits<7> opcode, bits<3> funct3, bits<7>funct7, string instr_asm, SDNode OpNode, RegisterClass RC> : MYRISCVX_R<opcode, funct3, funct7, (outs RC:$rd), (ins RC:$rs1, RC:$rs2), !strconcat(instr_asm, "\t$rd, $rs1, $rs2"), [(set RC:$rd, (OpNode RC:$rs1, RC:$rs2))], IIAlu> { let isReMaterializable = 1; } // Arithmetic and logical instructions with 2 register operands. class ArithLogicI<bits<7> opcode, bits<3> funct3, string instr_asm, SDNode OpNode, Operand Od, PatLeaf imm_type, RegisterClass RC> : MYRISCVX_I<opcode, funct3, (outs RC:$rd), (ins RC:$rs1, Od:$imm12), !strconcat(instr_asm, "\t$rd, $rs1, $imm12"), [(set RC:$rd, (OpNode RC:$rs1, imm_type:$imm12))], IIAlu> { let isReMaterializable = 1; } class ArithLogicU<bits<7> opcode, string instr_asm, RegisterClass RC, Operand Od, PatLeaf imm_type> : MYRISCVX_U<opcode, (outs RC:$rd), (ins Od:$imm20), !strconcat(instr_asm, "\t$rd, $imm20"), [], IIAlu> { let isReMaterializable = 1; }
//===----------------------------------------------------------------------===// // Instruction definition //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// // MYRISCVX Instructions //===----------------------------------------------------------------------===// def ADDI : ArithLogicI<0b0010011, 0b000, "addi", add, simm12, immSExt12, GPR>; def LUI : ArithLogicU<0b0110111, "lui", GPR, simm20, immSExt12>; def ADD : ArithLogicR<0b0110011, 0b000, 0b0000000, "add", add, GPR>; def SUB : ArithLogicR<0b0110011, 0b000, 0b0100000, "sub", sub, GPR>;
とりあえずこれだけだ。LUI, ADD, SUB, ADDI
に加えてさらにこれらの定義をラップするためのArithLogicR
, ArithLogicI
, ArithLogicU
を定義した。
これらは、似たような種類の命令を何度も定義するにあたり、同じような定義を何度も繰り返さないように作られたラップ用のクラスだ。
それぞれ、
ArithLogicR
:MYRISCVX_R
を継承ArithLogicI
:MYRISCVX_I
を継承ArithLogicU
:MYRISCVX_U
を継承
となっており、その意味はすぐに分かると思う。
一つADDI
命令に着目してみると、
def ADDI : ArithLogicI<0b0010011, 0b000, "addi", add, simm12, immSExt12, GPR>;
ArithLogicI
では、
opcode
= 0b0010011funct3
= 0b000instr_asm
= "addi"OpNode
= addOd
= simm12imm_type
= immSExt12RC
= GPR
となっている。opcode
とfunct3
は命令デコードをそのまま示しており、これは分かりやすいと思う。
そして、MYRISCVX_I
クラスにそのままこの値が渡されて、命令でコードに使用される。
instr_asm
は命令のニーモニックの表記だ。
ArithLogicI
内で!strconcat(instr_asm, "\t$rd, $rs1, $imm12"),
で、"addi"と"\t$rd, $rs1, $imm12"
がconcatされて文字列が接合され、addi\t$rd, $rs1, $imm12
が生成されていることが分かる。
RC
はレジスタクラスで、MYRISCVXRegisterInfo.td
で定義したGPR
を設定している。
このGPR内のどれかのレジスタ割り当てられることになる。
つぎにOd
だが、これはどのような即値情報が渡されるかを示している。
ADDI
の定義ではsimm12
が設定されている。
simm12
は、12ビットの即値だが、tdの定義を見ればわかるように、32ビット整数のクラスを継承している。
これは後で登場するが、ディスアセンブラを定義するとき、DecodeSimm12
というメソッドを定義してこの12ビットの即値をどのように表記するかを決定する。
imm_type
はPatLeaf
というクラスを継承して定義されたimmSExt12
が設定されている。
これは即値を定義するためのHelperクラスで、その内容を見ればわかるが、オペランド値を受け取り、それを12ビット目をMSBにして符号拡張するパタンになる。
最後に、OpNode
として設定されているadd
は、この命令が加算操作を行う命令であるということを示している。
ArithLogicI
内で、入力ノード、出力ノード、演算ノードを使用してどのような演算が作られるのか定義されている。
MYRISCVX_I<opcode, funct3, (outs RC:$rd), (ins RC:$rs1, Od:$imm12), !strconcat(instr_asm, "\t$rd, $rs1, $imm12"), [(set RC:$rd, (OpNode RC:$rs1, imm_type:$imm12))], IIAlu> { ...
つまり、
(outs RC:$rd)
:RC
レジスタクラスの一員として$rd
を定義する。これは出力ノードである。(ins RC:$rs1, Od:$imm12)
:RC
レジスタクラスの一員として$rs1
レジスタを定義する。そしてOd
クラスの一員として$imm12
クラスを定義する。これらは、入力ノードである。- この命令は、
$rs1
レジスタと即値$imm12
に対してOpNode
に基づく演算を実行し、その実行結果を$d
に設定する命令である。
ということが定義されました。ArithLogicR
についても同様だ。
MYRISCVX_R<opcode, funct3, funct7, (outs RC:$rd), (ins RC:$rs1, RC:$rs2), !strconcat(instr_asm, "\t$rd, $rs1, $rs2"), [(set RC:$rd, (OpNode RC:$rs1, RC:$rs2))], IIAlu> {
MYRISCVX_U
については、パタンの定義が行われていない([]
で空白となっている)。
これはMYRISCVX_U
で定義される命令はLUI
やAUIPC
など1つのテンプレートとして表現することができないので、とりあえず省略しているのだ。
MYRISCVX_U<opcode, (outs RC:$rd), (ins Od:$imm20), !strconcat(instr_asm, "\t$rd, $imm20"), [], IIAlu> {