先日, twitterでgccでCortex-M4のsqrtf()の呼び出しの代わりにFPU命令一発のコードを出力をさせる方法のことが話題に上っていて、少し調べたのですが、せっかくなのでブログにまとめておくことにしました。
なお, 私にとって初のQiita投稿です。
最初に結論
#include <math.h>
float a(float x)
{
return sqrtf(x);
}
これを以下のようにコンパイルして生成されたコードを見ると
arm-eabi-gcc -c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Ofast test_sqrtf.c
arm-eabi-objdump -d test_sqrtf.o
00000000 <a>:
0: eeb1 0ac0 vsqrt.f32 s0, s0
4: 4770 bx lr
6: bf00 nop
このように、sqrtf()の呼び出しの代わりにvsqrt.f32のFPU命令をひとつ実行するだけになります。
以降は試行錯誤した経緯や、詳細など。
Cortex-M4向けのコンパイルオプション
-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
Cortex-M4のFPUはARMの他のものと違って、単精度の浮動小数点数しか扱えません。-mfpu=fpv4-sp-d16 を忘れずに。
arm-eabi-gcc -c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 test_sqrtf.c
これでコンパイルするとこうなりました。
00000000 <a>:
0: b580 push {r7, lr}
2: b082 sub sp, #8
4: af00 add r7, sp, #0
6: ed87 0a01 vstr s0, [r7, #4]
a: ed97 0a01 vldr s0, [r7, #4]
e: f7ff fffe bl 0 <sqrtf>
12: eef0 7a40 vmov.f32 s15, s0
16: eeb0 0a67 vmov.f32 s0, s15
1a: 3708 adds r7, #8
1c: 46bd mov sp, r7
1e: bd80 pop {r7, pc}
sqrtf()はそのまま関数呼び出しとして残っています。
最適化オプション -O をつけてみる
arm-eabi-gcc -c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -O test_sqrtf.c
00000000 <a>:
0: b508 push {r3, lr}
2: ed2d 8b02 vpush {d8}
6: eeb1 8ac0 vsqrt.f32 s16, s0
a: eeb5 0a40 vcmp.f32 s0, #0.0
e: eef1 fa10 vmrs APSR_nzcv, fpscr
12: d404 bmi.n 1e <a+0x1e>
14: eeb0 0a48 vmov.f32 s0, s16
18: ecbd 8b02 vpop {d8}
1c: bd08 pop {r3, pc}
1e: f7ff fffe bl 0 <sqrtf>
22: e7f7 b.n 14 <a+0x14>
おお、FPU命令のvsqrt.f32が出ました!
でもなんかコードが長いし、sqrtf()の関数呼び出しも残ってる。
よくコードを見てみると、sqrtfの引数が負のときにsqrtf()を呼ぶようになっています。
これは、sqrtfの引数が負の場合にはエラーになり、変数errnoにエラーコードをセットするなどのエラー処理をする必要があります。そのためにライブラリ関数のsqrtf()を呼ぶのです。
しかし、マイコンのプログラミングの場合には変数errnoのセットはいらないから、少しでも速く短いほうがいいということも多いと思います。このように浮動小数点演算の厳密さよりも速さを優先したい場合にうってつけのコンパイルオプションが -ffast-math です。
そして、 -Ofast というのは -O3 -ffast-math と同じ意味です。
最適化オプション -Ofast をつけてみる
arm-eabi-gcc -c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Ofast test_sqrtf.c
これで冒頭のようなすっきりしたコードになります。
続き
Cortex-M4でFPU演算をするときにつけておきたいgccの警告オプション
参考
使用したgccはgcc-linaro-7.2.1-2017.11-x86_64_arm-eabi で以下のサイトからダウンロードしました。
https://releases.linaro.org/components/toolchain/binaries/latest/arm-eabi/
この記事で使ったコードはgistにも貼っておきました。
https://gist.github.com/tetsu-koba/5a174de0b146cb76b50e459d69800928
gccの最適化オプションについてはこちらを参照してください。
https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Optimize-Options.html#Optimize-Options