前回の制御構文を追加した際に、不要なジャンプ命令が発生しているのが気になる。例えば、LLVM IRをダンプし時に以下のようなIRが出力されたはずだ。この時、br label %if.end
はすぐ後ろのif.end
にジャンプするため、このジャンプ文は不要なはずだ。ここでは、このような不要なジャンプ命令を削除して、コードの最適化を図ってみる。
if.then: ; preds = %entry %1 = load i32, i32* %a, align 4 %inc = add i32 %1, 1 store i32 %inc, i32* %a, align 4 br label %if.end if.end: ; preds = %if.then, %entry %2 = load i32, i32* %a, align 4 ret i32 %2 }
最適化のPassを追加する
ここで最適化のPassを追加するのだが、マシンコードが生成される前に最適化のPassを追加する。この場合、TargetPassConfig::addPreEmitPass
を通じてPassを追加する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXTargetMachine.cpp
//@MYRISCVXPassConfig { /// MYRISCVX Code Generator Pass Configuration Options. class MYRISCVXPassConfig : public TargetPassConfig { public: ... void addPreEmitPass() override; ...
void MYRISCVXPassConfig::addPreEmitPass() { MYRISCVXTargetMachine &TM = getMYRISCVXTargetMachine(); addPass (createMYRISCVXDelJmpPass(TM)); return; }
createMYRISCVXDelJmpPass
というPassを追加した。このPassはMYRISCVXDelUselessJMP.cpp
で追加する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXDelUselessJMP.cpp
#define DEBUG_TYPE "del-jmp" STATISTIC(NumDelJmp, "Number of useless jmp deleted"); static cl::opt<bool> EnableDelJmp( "enable-MYRISCVX-del-useless-jmp", cl::init(true), cl::desc("Delete useless jmp instructions: jmp 0."), cl::Hidden);
Passの定義を行う。このPassはDelJmp
というもので、デフォルトで有効だ。-enable-MYRISCVX-del-useless-jmp
オプションで有効無効を制御することができる。例えば、-enable-MYRISCVX-del-useless-jmp=false
とすることでこのPassを無効化することができる。
DelJmp
クラスを定義する。このクラスは、MachineFunctionPass
クラスから派生している。MachineFunctionPass
クラスに記述したPassは、runOnMachineFunction()
関数を呼び出すことにより実行される。
virtual bool runOnMachineFunction(MachineFunction &MF) = 0;
では、DelJmp
クラスのrunOnMachineFunction()
関数を実装してみる。
bool runOnMachineFunction(MachineFunction &F) { bool Changed = false; if (EnableDelJmp) { MachineFunction::iterator FJ = F.begin(); if (FJ != F.end()) FJ++; if (FJ == F.end()) return Changed; for (MachineFunction::iterator FI = F.begin(), FE = F.end(); FJ != FE; ++FI, ++FJ) // In STL style, F.end() is the dummy BasicBlock() like '\0' in // C string. // FJ is the next BasicBlock of FI; When FI range from F.begin() to // the PreviousBasicBlock of F.end() call runOnMachineBasicBlock(). Changed |= runOnMachineBasicBlock(*FI, *FJ); } return Changed; }
このコードでは、マシンコードからBasicBlockをひとつづつ取り出してPassを適用するという形をとっている。FI
イテレータがターゲットとなるBasic Blockを示しており、一つずらしたFJ
イテレータがFI
の次のブロックを示している。つまり、
FI
イテレータの一番最後のコードがジャンプ命令であり、- ジャンプ先が
FJ
の先頭であれば、FI
の最後のジャンプ命令を削除しても良い
ということになり、この処理をrunOnMachineBasicBlock()
に記述している。最終的にコードの変更を行った場合は、Changed
変数をTrueに設定して戻る。
bool DelJmp:: runOnMachineBasicBlock(MachineBasicBlock &MBB, MachineBasicBlock &MBBN) { bool Changed = false; MachineBasicBlock::iterator I = MBB.end(); if (I != MBB.begin()) I--; // set I to the last instruction else return Changed; if (I->getOpcode() == MYRISCVX::J && I->getOperand(0).getMBB() == &MBBN) { // I is the instruction of "jmp #offset=0", as follows, // j $BB0_3 // $BB0_3: // ld $4, 28($sp) ++NumDelJmp; MBB.erase(I); // delete the "JMP 0" instruction Changed = true; // Notify LLVM kernel Changed } return Changed; }
runOnMachineBasicBasicBlock
では、BasicBlockMBB
と次のBasicBlockMBBN
を使って最適化処理を行っている。MBB
の最後の命令が対象となる命令で、イテレータI
で示されている。I
がRISC-VのJ
命令であり、さらに第1オペランドが示している先がMBBN
(次のBasic Blockの先頭)であれば、削除可能だ。MBB.erase(I)
により命令の削除を行い、Change
をTrueに設定、そして最適化を行った回数を示すNumDelJmp
をインクリメントする。
このNumDelJmp
変数は、llcの-stats
オプションにより参照することができる。
では、ここまでの変更を施したコードでLLVMをビルドしてみる。
# DelJmpPassを有効にした場合 ./bin/llc -debug -march=myriscvx32 -mcpu=simple32 -relocation-model=pic -filetype=asm ch8_1_br_simple.bc -stats -o -
# DelJmpPassを無効にした場合 ./bin/llc -debug -march=myriscvx32 -mcpu=simple32 -relocation-model=pic -enable-MYRISCVX-del-useless-jmp=false -filetype=asm ch8_1_br_simple.bc -o -
それぞれ生成されたコードは以下のようになる。
DelJmpPass
を無効にした場合
_Z14test_br_simplev: .frame $x8,8,$x1 .mask 0x00000000,0 .set noreorder .set nomacro # %bb.0: # %entry addi x2, x2, -8 addi x10, zero, 0 sb x10, 4(x2) lbu x10, 4(x2) andi x10, x10, 1 beq x10, zero, $BB0_2 j $BB0_1 $BB0_1: # %if.then addi x10, zero, 10 sw x10, 0(x2) j $BB0_3 $BB0_2: # %if.else addi x10, zero, 20 sw x10, 0(x2) j $BB0_3 $BB0_3: # %if.end lw x10, 0(x2) addi x2, x2, 8 ret x1 .set macro .set reorder .end _Z14test_br_simplev $func_end0:
DelJmpPass
を有効にした場合
_Z14test_br_simplev: .frame $x8,8,$x1 .mask 0x00000000,0 .set noreorder .set nomacro # %bb.0: # %entry addi x2, x2, -8 addi x10, zero, 0 sb x10, 4(x2) lbu x10, 4(x2) andi x10, x10, 1 beq x10, zero, $BB0_2 # %bb.1: # %if.then addi x10, zero, 10 sw x10, 0(x2) j $BB0_3 $BB0_2: # %if.else addi x10, zero, 20 sw x10, 0(x2) $BB0_3: # %if.end lw x10, 0(x2) addi x2, x2, 8 ret x1 .set macro .set reorder .end _Z14test_br_simplev $func_end0:
不要なJ命令が削除されているのが分かる。不要なJ命令がどれだけ削除されたのか-stats
オプションを付けて確認してみようと思う。
./bin/llc -march=myriscvx32 -mcpu=simple32 -relocation-model=pic -stats -filetype=asm ch8_1_br_simple.bc -o -
===-------------------------------------------------------------------------=== ... Statistics Collected ... ===-------------------------------------------------------------------------=== 14 asm-printer - Number of machine instrs printed 35 bitcode-reader - Number of Metadata records loaded 2 bitcode-reader - Number of MDStrings loaded 6 dagcombine - Number of dag nodes combined 2 del-jmp - Number of useless jmp deleted 4 isel - Number of blocks selected using DAG 4 isel - Number of times dag isel has to try another path 1 isel - Number of entry blocks encountered 8 prologepilog - Number of bytes used for stack in all functions 1 prologepilog - Number of functions seen in PEI 6 regalloc - Number of registers assigned 1 regalloc - Number of identity moves eliminated after rewriting 1 stackmaps - Number of functions skipped 1 stackmaps - Number of functions visited
del-jmp
Passにより、2つの命令が削除されていることが分かる。