LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。
第11章では、アセンブラやIntrinsicをサポートする。今度はC言語の中にIntrinsicを埋め込んだアセンブラの拡張を行う。例えば、以下のようなコードをサポートする。
__asm__ __volatile__("addi %0,%1,%2" :"=r"(foo) // 15 :"r"(foo), "i"(5) ); __asm__ __volatile__("ld %0,%1" :"=r"(c) // c=3 :"m"(g[2]) );
インラインアセンブリである。組み込みの業界ではよく使う手法なのだが。上記のaddi
では、
- オペランド1 :
=r(foo)
に相当。レジスタ指定で書き込みレジスタとして扱われる。このレジスタは最終的に変数foo
として扱われる。 - オペランド2 :
r(foo)
に相当。レジスタ指定で読み込みレジスタとして扱われる。このレジスタは最終的に変数foo
として扱われる。 - オペランド3 :
i(5)
に相当。即値指定で5が設定される。
これらの表現をサポートするためには、MYRISCVXのAsmPrinter
を改造する必要がある。
追加するのは、以下のコードだ。以下のコードをオーバライドし、MYRISCVX用に改造する。
// インラインアセンブリの式のオペランドをプリントする。 bool PrintAsmOperand(const MachineInstr *MI, unsigned OpNo, unsigned AsmVariant, const char *ExtraCode, raw_ostream &O) override; // メモリアドレスのオペランド bool PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNum, unsigned AsmVariant, const char *ExtraCode, raw_ostream &O) override; // オペランドのプリント void printOperand(const MachineInstr *MI, int opNum, raw_ostream &O);
MYRISCVXTargetLowering::getConstraintType ()
: ターゲットの制約を取得する。デフォルトでは、最初から組み込まれている
TargetLowering::getConstraintType(Constraint)
が呼ばれる。しかし、それ以外に特殊なケースについて
switch
文が組み込まれている。これはどこから来ているかというと、MIPSの規定として以下の特殊なイントリンジックの表記が使えるとあり、ここから来ているものと思われる。 https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints
このなかで、
c
: A register suitable for use in an indirect jump. This will always be$25
for -mabicalls.R
: An address that can be used in a non-macro load or store.- c
の場合は
C_RegisterClass,
Rの場合は
C_Memory`を返す。
例えば、以下のようにIntrinsicを使用してみる。
int foo = 10; const int bar = 15; __asm__ __volatile__("add %0,%1,%2" :"=r"(foo) // 5 :"c"(foo), "c"(bar) );
addi 10, zero, 15 sw 10, 4(2) lw 5, 8(2) addi 5, 10, 0 #APP add $11,$5,$5 #NO_APP
このように、アーキテクチャ独自のIntrinsicの記法を導入することができるようだ。
MYRISCXVTargetLowering::getSingleConstraintMatchWeight ()` :
std::pair<unsigned, const TargetRegisterClass *> Cpu0TargetLowering::getRegForInlineAsmConstraint()
- constraintに応じて、レジスタを取得しする。つまり、今回のケースだと
r
とc
の場合にMYRISCVX::GPRRegClass
を返する。c
の場合には、優先的にt0 (=x5)
が使用されるようになる。
- constraintに応じて、レジスタを取得しする。つまり、今回のケースだと
void Cpu0TargetLowering::LowerAsmOperandForConstraint()
bool Cpu0TargetLowering::isLegalAddressingMode()
- アドレッシングモードが正しく記述されているかどうかをチェックする。
上記の実装を追加した結果、以下のようにIntrisicを含んだソースコードがコンパイルできるようになった。
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch11_2.cpp -emit-llvm ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=obj ch11_2.bc -o -
_Z16inlineasm_globalv: .frame $8,16,$1 .mask 0x00000100,-4 .set noreorder .cpload $t9 .set nomacro # %bb.0: # %entry lui 10, %hi(_gp_disp) addi 10, 10, %lo(_gp_disp) addi 2, 2, -16 sw 8, 12(2) # 4-byte Folded Spill move 8, 2 lui 10, %got_hi(g) add 10, 10, 3 lw 10, %got_lo(g)(10) addi 11, 10, 8 #APP lw $11,0($11) #NO_APP sw 11, 8(2) lw 11, 8(2) #APP addi $11,$11,1 #NO_APP sw 11, 4(2) lw 10, 4(2) move 2, 8 lw 8, 12(2) # 4-byte Folded Reload addi 2, 2, 16 jalr 1