関数に関連する命令の生成の続き。関数のプロローグ・エピローグとはどのような処理をするのだろうか。
- プロローグ : 関数の先頭で、関数のスタックフレームを生成する。関数が使用する変数のサイズだけスタックを移動し、メモリ領域を確保する。また、Calling ConventionにおけるCallee Savedレジスタを保存する。このために、
emitPrologue()
関数を実装する。 - エピローグ : プロローグと逆の処理を実行する。つまり、使用したスタックフレームを元に戻す。このために、
emitEpilogue()
を実装する。
これらの処理のため、関数フレームを処理するためのクラスMYRISCVXFrameLowering
を作成していた。
MYRISCVXFrameLowering::emitPrologue()
emitPrologue
は関数のエピローグのコードを生成する。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXFrameLowering.cpp
void MYRISCVXFrameLowering::emitPrologue(MachineFunction &MF, MachineBasicBlock &MBB) const {
引数としては2つ用意されている。MachineFunction MF
は変換対象となる関数の実体で、関数内部のBasicBlockなどを含んでいる。MachieBasicBlockのインスタンスのリストを含んでおり、MachineFunction
にはMachineControlPool
, MachineFrameInfo
, MachineFunctionInfo
, MachineRegisterInfo
クラスが含まれている。
MachineBasicBlock MBB
は変換後のBasicBlockです。MachineBasicBlockにはマシン命令のリストであるMachineInstr
のリストが含まれている。
emitPrologue()
内では関数内で使用されるスタックのサイズを計算し、そのその分だけスタックポインタの量を調整する。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXFrameLowering.cpp
void MYRISCVXFrameLowering::emitPrologue(MachineFunction &MF, MachineBasicBlock &MBB) const { ... // First, compute final stack size. uint64_t StackSize = MFI.getStackSize(); // No need to allocate space on the stack. if (StackSize == 0 && !MFI.adjustsStack()) return; MachineModuleInfo &MMI = MF.getMMI(); const MCRegisterInfo *MRI = MMI.getContext().getRegisterInfo(); // Adjust stack. TII.adjustStackPtr(SP, -StackSize, MBB, MBBI); ...
adjustStackPtr()
関数により、実際にスタックポインタをデクリメントする命令を生成する。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.cpp
/// Adjust SP by Amount bytes. void MYRISCVXInstrInfo::adjustStackPtr(unsigned SP, int64_t Amount, MachineBasicBlock &MBB, MachineBasicBlock::iterator I) const { DebugLoc DL = I != MBB.end() ? I->getDebugLoc() : DebugLoc(); unsigned ADD = MYRISCVX::ADD; unsigned ADDI = MYRISCVX::ADDI; if (isInt<12>(Amount)) { // addiu sp, sp, amount BuildMI(MBB, I, DL, get(ADDI), SP).addReg(SP).addImm(Amount); } else { // Expand immediate that doesn't fit in 12-bit. // unsigned Reg = MRI.createVirtualRegister(&MYRISCVX::GPRRegClass); unsigned Reg = MYRISCVX::T0; loadImmediate(Amount, MBB, I, DL, Reg, nullptr); BuildMI(MBB, I, DL, get(ADD), SP).addReg(SP).addReg(Reg, RegState::Kill); } }
adjustStackPtr
の実装は見ればすぐにわかると思う。Amount
の量が12ビット以内に収まる数値であれば、単純にADDI
命令を生成して、スタックポインタを更新する。12ビットを超える値の場合は、loadImmediate()
関数を呼び出して即値を生成し、この値をT0レジスタに格納する。T0レジスタの値をSPレジスタと加算してスタックポインタを更新する。
loadImmediate()
の実装を以下に示する。MYRISCVXInstrInfo
に実装しました。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.cpp
/// This function generates the sequence of instructions needed to get the /// result of adding register REG and immediate IMM. void MYRISCVXInstrInfo::loadImmediate(int64_t Imm, MachineBasicBlock &MBB, MachineBasicBlock::iterator II, const DebugLoc &DL, unsigned DstReg, unsigned *NewImm) const { uint64_t Hi20 = ((Imm + 0x800) >> 12) & 0xfffff; uint64_t Lo12 = SignExtend64<12>(Imm); BuildMI(MBB, II, DL, get(MYRISCVX::LUI), DstReg) .addImm(Hi20); BuildMI(MBB, II, DL, get(MYRISCVX::ADDI), DstReg) .addReg(DstReg, RegState::Kill) .addImm(Lo12); }
最初にLUI命令により上位の20ビットを生成し、次に下位の12ビットを生成する。
MYRISCVXFrameLowering::emitEpilogue()
また、EmitEpilogueではその逆で、関数内のスタックサイズの分だけスタックポインタを元に戻す。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXFrameLowering.cpp
//@emitEpilogue { void MYRISCVXFrameLowering::emitEpilogue(MachineFunction &MF, MachineBasicBlock &MBB) const { ... unsigned SP = MYRISCVX::SP; // Get the number of bytes from FrameInfo uint64_t StackSize = MFI.getStackSize(); if (!StackSize) return; // Adjust stack. TII.adjustStackPtr(SP, StackSize, MBB, MBBI); ...