如果关闭分层编译,执行GVN优化前会使用ShortLoopOptimizer做一些简单的循环优化,其中就包括循环不变代码提升(Loop Invariant Code Motion,LCM)。LCM是指将循环中不变的值移动到循环外面,消除每次进行计算的必要:
void LoopInvariantCodeMotion::process_block(BlockBegin* block) {
...
// 形参表示位于循环的所有基本块。遍历基本块中的每一条指令
while (cur != NULL) {
bool cur_invariant = false;
// 指令是常量且不能发生trap;指令是算数/逻辑/位运算,操作数都是不变量且不能发生trap
// 指令读取字段值,字段不是volatile,或者是读取数组中的某个元素,数组是不变量等;
// 指令获取数组长度,且数组长度是不变量。如果发生上述条件之一,那么该指令是循环不变量
if (cur->as_Constant() != NULL) {
cur_invariant = !cur->can_trap();
} else if (cur->as_ArithmeticOp() != NULL || cur->as_LogicOp() != NULL || cur->as_ShiftOp() != NULL) {
Op2* op2 = (Op2*)cur;
cur_invariant = ...;
} else if (cur->as_LoadField() != NULL) {
cur_invariant = ...;
} else if (cur->as_ArrayLength() != NULL) {
ArrayLength *length = cur->as_ArrayLength();
cur_invariant = is_invariant(length->array());
} else if (cur->as_LoadIndexed() != NULL) {
LoadIndexed *li = (LoadIndexed *)cur->as_LoadIndexed();
cur_invariant = ...;
}
// 如果该指令是循环不变量
if (cur_invariant) {
...
// 将该指令从循环内部移动到循环前面
Instruction* next = cur->next();
Instruction* in = _insertion_point->next();
_insertion_point = _insertion_point->set_next(cur);
cur->set_next(in);
...
} else {
prev = cur;
cur = cur->next();
}
}
}
LCM遍历构成循环的所有基本块,然后遍历基本块的每一条指令,当发现满足要求的循环不变量时,将循环不变量从循环基本块中移除,然后添加到insertion_point所在的基本块,insertion_point即支配循环头的基本块,具体到例子中,如下Java代码:
public static void loopInvariant(){
class LoopInvariantMotion {
private static int[] arr = new int[]{1,2,3,4};
public static void loopInvariant(){
int s = 0;
for(int i=0;i<10;i++){
s += arr.length; // 循环不变量arr.length
s += arr[2]; // 循环不变量arr[2]
}
}
}
它对应的HIR如下图所示,其中B1和B2构成for循环,B0基本块支配B1基本块:
当发现循环基本块B2中的两个不变量后,C1会将它移到循环外面的B0基本块,该基本块支配循环头基本块B1,如下图所示: