
最後に、アセンブリの生成です。TAILCALLとTAILCALL_Rは疑似命令なのでそのままではアセンブリ命令を出力することができない。これを置き換える処理は、tdファイルからすでに生成されている。MYRISCVXGenMCPseudoLowering.incを見てみみる。
build-myriscvx/lib/Target/MYRISCVX/MYRISCVXGenMCPseudoLowering.inc
bool MYRISCVXAsmPrinter:: emitPseudoExpansionLowering(MCStreamer &OutStreamer, const MachineInstr *MI) { switch (MI->getOpcode()) { default: return false; case MYRISCVX::TAILCALL: { MCInst TmpInst; MCOperand MCOp; TmpInst.setOpcode(MYRISCVX::JAL); // Operand: target lowerOperand(MI->getOperand(0), MCOp); TmpInst.addOperand(MCOp); EmitToStreamer(OutStreamer, TmpInst); break; } case MYRISCVX::TAILCALL_R: { MCInst TmpInst; MCOperand MCOp; TmpInst.setOpcode(MYRISCVX::JALR); // Operand: rs1 lowerOperand(MI->getOperand(0), MCOp); TmpInst.addOperand(MCOp); EmitToStreamer(OutStreamer, TmpInst); break; } } return true; }
TAILCALLではJAL命令に置き換え、TAILCALL_RノードではJALR命令に置き換えるシーケンスが生成されていることが分かる。なぜこれが生成されたかというと、これらのノードにはPseudoInstExpansionが追加されており、自動的に展開することができるようになっていたからです。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.cpp
//- EmitInstruction() must exists or will have run time error. void MYRISCVXAsmPrinter::EmitInstruction(const MachineInstr *MI) { ... do { // Do any auto-generated pseudo lowerings. if (emitPseudoExpansionLowering(*OutStreamer, &*I)) continue; ...
ここまでで、末尾関数呼び出し最適化を適用した場合とそうでない場合についてアセンブリ命令を生成してみる。
# 末尾関数呼び出し最適化を適用した場合
./bin/llc -stats -debug -target-abi=lp64 -march=myriscvx32 -mcpu=simple32 -enable-MYRISCVX-tail-calls=true -relocation-model=static -filetype=asm result/tailcall_-O1_-march=myriscvx32_-mcpu=simple32_static_lp64_-enable-MYRISCVX-tail-calls=true/tailcall.bc -o -
# 末尾関数呼び出し最適化を適用しない場合
./bin/llc -stats -debug -target-abi=lp64 -march=myriscvx32 -mcpu=simple32 -enable-MYRISCVX-tail-calls=false -relocation-model=static -filetype=asm result/tailcall_-O1_-march=myriscvx32_-mcpu=simple32_static_lp64_-enable-MYRISCVX-tail-calls=false/tailcall.bc -o -
- 末尾関数呼び出し最適化を適用した場合の生成されたアセンブリ命令
```asm
// 関数呼び出され側 (Callee)
_Z14tail_call_funcii:
...
# %bb.0: # %entry
add x10, x11, x10
ret x1
// 関数呼び出し側 (Caller)
_Z14tail_call_mainiiii:
...
# %bb.0: # %entry
sub x10, x10, x11
mul x11, x13, x12
jal _Z14tail_call_funcii
...
- 末尾関数呼び出し最適化を適用しない場合の生成されたアセンブリ命令
// 関数呼び出され側 (Callee) _Z14tail_call_funcii: ... # %bb.0: # %entry add x10, x11, x10 ret x1 _Z14tail_call_mainiiii: ... # %bb.0: # %entry addi x2, x2, -8 .cfi_def_cfa_offset 8 sw x2, 4(x2) # 4-byte Folded Spill .cfi_offset 2, -4 sub x10, x10, x11 mul x11, x13, x12 jal _Z14tail_call_funcii lw x2, 4(x2) # 4-byte Folded Reload addi x2, x2, 8 ret x1
末尾関数呼び出し最適化を適用しない場合場合は、_Z14tail_call_funcii呼び出し前にスタックの調整が行われていることが確認できる。一方で、末尾関数呼び出し最適化を適用した場合はスタックの調整コードが省略されている。これにより、関数呼び出しの場合のスタックフレームの消費を抑え、動作を高速化することができる。