関数のプロローグ・エピローグに必要な新規命令の実装
関数のプロローグ・エピローグの処理に入る前に、少し新規命令を実装していく。これは、関数のプロローグ・エピローグの実装に際して必要なものだ。
定数生成パタンの実装
まずは最も基本的な定数を生成するためのパタンを定義する。定数を生成できなければ、関数のオフセットや、固定値を作ることができない。
まず、算術演算命令を生成させる前に、テストなどで頻繁に出てくる定数を生成させるパタンをLLVMに追加する。例えば、0x01234567
という32ビット定数をレジスタに格納したい場合、MYRISCVXはどのように命令を生成すればよいのだろうか。
RISC-Vには、lui
命令というものが定義されている。
lui
命令は20ビットの即値オペランドを持ち、32ビットのうち上位の20ビットにオペランドの値を設定する。
さらに、ori
命令は12ビットの即値オペランドを取ることができるので、まずは上位の20ビットを設定して、ori
命令で下位の12ビットを設定すれば、任意の32ビット値を生成できると考えられる。
このルールをMYRISCVXInstrInfo.td
に追加すればよいことが分かる。ここでは、以下の3つルールを追加する。
- 12ビット以内に収まる符号付き定数 :
immSExt12
は、以下のルールで表現される。
def immSExt12 : PatLeaf<(imm), [{ return isInt<12>(N->getSExtValue()); }]>;
これは、$zeroレジスタとのaddi
で生成することができる。符号付きなので、addi
命令を使う。
def : Pat<(i32 immSExt12:$in), (ADDI ZERO, imm:$in)>;
- 12ビット以内に収まる符号なし定数 :
immZExt12
は、以下のルールで表現される。
def immZExt12 : PatLeaf<(imm), [{ if (N->getValueType(0) == MVT::i32) return (uint32_t)N->getZExtValue() == (unsigned short)N->getZExtValue(); else return (uint64_t)N->getZExtValue() == (unsigned short)N->getZExtValue(); }], LO12>;
これは、addi
の代わりにori
を使用する。
def : Pat<(i32 immZExt12:$in), (ORI ZERO, imm:$in)>;
- 下位12ビットが0の定数 :
immLow12Zero
は、以下のルールで表現される。
// Immediate can be loaded with LUi (32-bit int with lower 16-bit cleared). def immLow12Zero : PatLeaf<(imm), [{ int64_t Val = N->getSExtValue(); return isInt<32>(Val) && !(Val & 0x0fff); }]>;
これは、単純にlui
命令だけでよいだろう。
def : Pat<(i32 immLow12Zero:$in), (LUI (HI20 imm:$in))>;
- 32ビット整数 :
imm
は、以下の手順で生成する。まずはlui
命令で上位の20ビットを作り、次にori
で買いの12ビットを連結する。
def : Pat<(i32 imm:$imm), (ORI (LUI (HI20 imm:$imm)), (LO12 imm:$imm))>;
// Transformation Function - get the lower 12 bits. def LO12 : SDNodeXForm<imm, [{ return getImm(N, N->getZExtValue() & 0xfff); }]>; // Transformation Function - get the higher 20 bits. def HI20 : SDNodeXForm<imm, [{ return getImm(N, (N->getZExtValue() >> 12) & 0xfffff); }]>;