次にコンパイルしたいのは、以下のようなCコードだ。
inline_assembly2.cpp
int global_data = 200; void test () { int add_res; int b = 200, c = 300; __asm__ __volatile__("add %0,%1,%2" :"=r"(add_res) :"r"(b), "r"(c) ); int lw_res; int *lw_p = (int *)&global_data; __asm__ __volatile__("lw %0, %1" :"=r"(lw_res) :"m"(lw_p) ); const int imm = 100; int addi_res; __asm__ __volatile__("addi %0,%1,%2" :"=r"(addi_res) // e=4 :"r"(lw_res),"i"(imm) ); }
インラインアセンブリだ。組み込みの業界ではよく使う手法だ。上記のaddi
では、
- オペランド1 :
=r(addi_res_)
に相当。レジスタ指定で書き込みレジスタとして扱われる。このレジスタは最終的に変数addi_res
として扱われる。 - オペランド2 :
r(lw_res_)
に相当。レジスタ指定で読み込みレジスタとして扱われる。このレジスタは最終的に変数lw_res
として扱われる。 - オペランド3 :
i(imm)
に相当。即値指定で100が設定される。
この表現をサポートするためには、どのようにすればよいのだろうか。最終的にはAsmPrinterを使用してMCInstを生成するとして、それまでにこれらのオペランドの特殊形式を検査し、該当するメモリアドレス、レジスタ、即値を当てはめていく必要がある。
アセンブリ表記の改良を行うためには、AsmPrinter
クラスの機能を使用する。ここでは、AsmPrinter
を拡張して、これらの拡張インラインアセンブリを処理できるようにする。
追加するのは、以下のコードだ。以下のコードをオーバライドし、MYRISCVX用に改造する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.h
// インラインアセンブリのオペランドを表示する。 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);
PrintAsmOperand()
: この関数はAsmPrinter::EmitFunctionBody()
→AsmPrinter::EmitInlineAsm()
の中で呼ばれる。オーバーライドしていなければ通常のPrintAsmOperand()
が呼ばれるが、今回はMYRISCVXAsmPrinter.cpp
内のPrintAsmOperand()
が呼び出される。
// Print out an operand for an inline asm expression. bool MYRISCVXAsmPrinter::PrintAsmOperand(const MachineInstr *MI, unsigned OpNum, unsigned AsmVariant,const char *ExtraCode, raw_ostream &O) { // Does this asm operand have a single letter operand modifier? if (ExtraCode && ExtraCode[0]) { if (ExtraCode[1] != 0) return true; // Unknown modifier. ... } printOperand(MI, OpNum, O); return false; }
PrintAsmOperand()
の先頭では、インラインアセンブリの方言を処理している(xxx)。最終的にprintOperand()
がオペランドを印刷する処理の本体だ。
printOperand()
は以下のように実装されている。
void MYRISCVXAsmPrinter::printOperand(const MachineInstr *MI, int opNum, raw_ostream &O) { const MachineOperand &MO = MI->getOperand(opNum); bool closeP = false; if (MO.getTargetFlags()) closeP = true; switch(MO.getTargetFlags()) { case MYRISCVXII::MO_GPREL : O << "%gp_rel(" ; break; case MYRISCVXII::MO_GOT_CALL : O << "%call16(" ; break; case MYRISCVXII::MO_GOT : O << "%got(" ; break; case MYRISCVXII::MO_ABS_HI : O << "%hi(" ; break; case MYRISCVXII::MO_ABS_LO : O << "%lo(" ; break; case MYRISCVXII::MO_GOT_HI20 : O << "%got_hi20(" ; break; case MYRISCVXII::MO_GOT_LO12 : O << "%got_lo12(" ; break; } switch (MO.getType()) { case MachineOperand::MO_Register: O << StringRef(MYRISCVXInstPrinter::getRegisterName(MO.getReg())).lower(); break; case MachineOperand::MO_Immediate: O << MO.getImm(); break; case MachineOperand::MO_MachineBasicBlock: O << *MO.getMBB()->getSymbol(); return; case MachineOperand::MO_GlobalAddress: O << *getSymbol(MO.getGlobal()); break; case MachineOperand::MO_BlockAddress: { MCSymbol *BA = GetBlockAddressSymbol(MO.getBlockAddress()); O << BA->getName(); break; } case MachineOperand::MO_ExternalSymbol: O << *GetExternalSymbolSymbol(MO.getSymbolName()); break; case MachineOperand::MO_JumpTableIndex: O << MAI->getPrivateGlobalPrefix() << "JTI" << getFunctionNumber() << '_' << MO.getIndex(); break; case MachineOperand::MO_ConstantPoolIndex: O << MAI->getPrivateGlobalPrefix() << "CPI" << getFunctionNumber() << "_" << MO.getIndex(); if (MO.getOffset()) O << "+" << MO.getOffset(); break; default: llvm_unreachable("<unknown operand type>"); } if (closeP) O << ")"; }
MachineOperand
の形式に応じて出力形式を作っていく。MachineOperand::MO_Register
, MachineOperand::MO_Immediate
, MachineOperand::MO_MachineBasicBlock
あたりを見れば、何をしているのか分かると思う。
PrintAsmMemoryOperand()
: メモリオペランドの印刷だ。メモリオペランドは一度レジスタに格納して与えられるので、即値0のオフセットと、汎用レジスタの組み合わせoffset(GPR)
の形式で与える。
bool MYRISCVXAsmPrinter::PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNum, unsigned AsmVariant, const char *ExtraCode, raw_ostream &O) { int Offset = 0; // Currently we are expecting either no ExtraCode or 'D' if (ExtraCode) { return true; // Unknown modifier. } const MachineOperand &MO = MI->getOperand(OpNum); assert(MO.isReg() && "unexpected inline asm memory operand"); O << Offset << "(" << MYRISCVXInstPrinter::getRegisterName(MO.getReg()) << ")"; return false; }
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`を返す。
例えば、以下のようにインラインアセンブリを使用してみる。
```cpp int foo = 10; const int bar = 15;
asm volatile("add %0,%1,%2" :"=r"(foo) // 5 :"c"(foo), "c"(bar) ); ```
asm addi 10, zero, 15 sw 10, 4(2) lw 5, 8(2) addi 5, 10, 0 #APP add $11,$5,$5 #NO_APP
このように、アーキテクチャ独自のインラインアセンブリの記法を導入することができる。
MYRISCXVTargetLowering::getSingleConstraintMatchWeight ()
:std::pair<unsigned, const TargetRegisterClass *> Cpu0TargetLowering::getRegForInlineAsmConstraint()
- constraintに応じて、レジスタを取得する。つまり、今回のケースだと
r
とc
の場合にMYRISCVX::GPRRegClass
を返す。c
の場合には、優先的にt0 (=x5)
が使用されるようになる。
- constraintに応じて、レジスタを取得する。つまり、今回のケースだと
void Cpu0TargetLowering::LowerAsmOperandForConstraint()
bool Cpu0TargetLowering::isLegalAddressingMode()
- アドレッシングモードが正しく記述されているかどうかをチェックする。
上記の実装を追加した結果、以下のようにインラインアセンブリを含んだソースコードがコンパイルできるようになった。
./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