最後に、アセンブリの生成です。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
呼び出し前にスタックの調整が行われていることが確認できる。一方で、末尾関数呼び出し最適化を適用した場合はスタックの調整コードが省略されている。これにより、関数呼び出しの場合のスタックフレームの消費を抑え、動作を高速化することができる。