ComProcコンパイラの最適化機能
現状のコンパイラの出力
code:led.c
void delay_ms(int ms) {
int *t = 2;
*t = ms;
while (*t > 0) {}
}
void led_out(int val) {
char *p = 0x80;
*p = val;
delay_ms(500);
*p = 0;
delay_ms(500);
}
int main() {
led_out(0xaa);
led_out(0x55);
return 0;
}
これをコンパイルすると以下を出力する。
コンパイラバージョンはコミットd129865
67ワード
code:led.asm
add fp,0x100
call main
st 0x82
fin:
jmp fin
delay_ms:
cpush fp
st cstack+0x0
add fp,0x2
add fp,0x2
push 2
push cstack+0x2
sta
pop
push cstack+0x0
ldd
push cstack+0x2
ldd
std
pop
L_0:
push cstack+0x2
ldd
ldd
push 0
lt
push 0
neq
jz L_1
jmp L_0
L_1:
cpop fp
ret
led_out:
cpush fp
st cstack+0x0
add fp,0x2
add fp,0x2
push 128
push cstack+0x2
sta
pop
push cstack+0x0
ldd
push cstack+0x2
ldd
std.1
pop
push 500
call delay_ms
pop
push 0
push cstack+0x2
ldd
std.1
pop
push 500
call delay_ms
pop
cpop fp
ret
main:
cpush fp
add fp,0x0
push 170
call led_out
pop
push 85
call led_out
pop
push 0
cpop fp
ret
簡単にできそうな最適化
(最適化と言わないような、無駄な命令を出さないようにする改造も含めて。)
変数定義に出会うたびに add fp,0x2 を繰り返すことを止めて、一気に FP を調整する。
push cstack+Nとlddが連続する部分はld cstack+Nに短縮する。
スタックのゴミを掃除するためのpopは不要かもしれない。
以上を適用すると、こうなるはず。
67ワード→47ワード(20ワード削減)
code:led.asm
add fp,0x100
call main
st 0x82
fin:
jmp fin
delay_ms:
cpush fp
st cstack+0x0
add fp,0x4 ; 2 つの add fp をまとめて実行
push 2
st cstack+0x2 ; push cstack+0x2 と sta をまとめて実行
ld cstack+0x0 ; push cstack+0x0 と ldd をまとめて実行
ld cstack+0x2 ; push cstack+0x2 と ldd をまとめて実行
std
L_0:
ld cstack+0x2 ; push cstack+0x2 と ldd をまとめて実行
ldd
push 0
lt
push 0
neq
jz L_1
jmp L_0
L_1:
cpop fp
ret
led_out:
cpush fp
st cstack+0x0
add fp,0x4 ; 2 つの add fp をまとめて実行
push 128
st cstack+0x2 ; push cstack+0x2 と sta をまとめて実行
ld cstack+0x0 ; push cstack+0x0 と ldd をまとめて実行
ld cstack+0x2 ; push cstack+0x2 と ldd をまとめて実行
std.1
push 500
call delay_ms
push 0
ld cstack+0x2 ; push cstack+0x2 と ldd をまとめて実行
std.1
push 500
call delay_ms
cpop fp
ret
main:
cpush fp ; add fp,0 を除去
push 170
call led_out
push 85
call led_out
push 0
cpop fp
ret
定数伝播解析を用いた最適化
code:led.c
void delay_ms(int ms) {
int *t = 2;
*t = ms;
while (*t > 0) {}
}
変数tは初期値以外に変更されない = 定数が伝播する
そのため、tを読み出す代わりに定数2を用いることが可能
code:「簡単にできそうな最適化」を適用したプログラム.asm
ld cstack+0x2 ; t が保持する値を読み出す(2 が読み出される)
ldd ; アドレス 2 から値を読み出す
code:定数伝播解析による最適化を適用したプログラム.asm
ld 2 ; アドレス 2 から値を読み出す
led_out関数のpも同様に最適化が可能
引数保存の省略
通常、関数の引数はスタックフレームの先頭付近に保存する。
ローカル変数と同様の扱いになる。
void delay_ms(int ms)の引数は1回しか使用されない。
ループの中ではない場所に1回だけ登場する。
そのため、メモリ上に保存する必要がない。
code:引数保存の省略をしないバージョン.asm
ld cstack+0x0 ; ms の値を読み出す
ld cstack+0x2 ; t の値を読み出す(タイマレジスタのアドレス)
std ; t が指す先へ ms の値を書く
code:引数保存を省略したバージョン.asm
ld cstack+0x2 ; t の値を読み出す(タイマレジスタのアドレス)
std ; t が指す先へ ms の値を書く
定数伝播解析による最適化も適用すれば st 2 という1命令になる。