2012年04月16日
ARM Cortex-A15の整数除算命令
ARMのCPUにはいままでずっと整数の除算命令がありませんでした。最近になって、Cortex-MシリーズとRシリーズには除算命令が追加されましたが、Cortex-AシリーズではCortex-A9までは除算命令はありませんでした。新しいCortex-A15には除算命令が追加されていました。
新しいgccとqemuはすでにこれに対応済みなので、試してみました。
Cortex-A15の整数除算命令
ARM Architecture Reference Manual を見ると、ARMv7-A の"The Virtulization Extensions" ではSDIVとUDIVの命令の実装を含むと書いてあります。
ARMv7-R profileではSCTLR.DZ bitでゼロ除算のときに例外を発生させるかどうかを設定することができますが、ARMv7-A profileの実装ではSDIVとUDIV命令はゼロ除算では常に例外を発生させることなく0を返すと書いてあります。
gccとqemuで動作を試す
Ubuntu 12.04 Beta2 を使用しています。
テストプログラム
$ cat idiv.c #include <stdio.h> #include <limits.h> int idiv(int x, int y) { return x / y; } int main() { int x0, y0; x0 = 101; y0 = 3; printf("%d / %d = %d\n", x0, y0, idiv(x0, y0)); x0 = INT_MIN; y0 = -1; printf("%d / %d = %d\n", x0, y0, idiv(x0, y0)); x0 = 101; y0 = 0; printf("%d / %d = %d\n", x0, y0, idiv(x0, y0)); }
gccの生成コードを見てみる
$ arm-linux-gnueabi-gcc -O -S -o idiv_arm.s idiv.c
特にCPUを指定せずにコンパイルすると以下のように整数除算では__aeabi_idivというコンパイラのランタイムライブラリを呼び出します。
idiv: @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 push {r3, lr} bl __aeabi_idiv pop {r3, pc}
$ arm-linux-gnueabi-gcc -mcpu=cortex-a15 -O -S -o idiv_a15.s idiv.c
CPUをcortex-a15に指定してコンパイルすると以下のようにSDIV命令を使用するようになります。
idiv: @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. sdiv r0, r0, r1 bx lr
除数が0かどうかチェックするコードが挿入されるかと思いましたがそうはなっていませんね。
ユーザーモードqemuで動作を見てみる
コンパイル、リンクして実行オブジェクトを作ります。
$ arm-linux-gnueabi-gcc -mcpu=cortex-a15 -O -o idiv_a15 idiv.c $ arm-linux-gnueabi-gcc -O -o idiv_arm idiv.c $ file idiv_a15 idiv_arm idiv_a15: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.31, BuildID[sha1]=0x6920c8ce8d71f1a7e040bfa53823357e672943fd, not stripped idiv_arm: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.31, BuildID[sha1]=0x64eb7fac6ca05f180e2dcc7209e9986019d9d7bb, not stripped
bimfmtに登録されているqemu-arm-static で実行してみます。CPU種別は "any" になっています。
$ ./idiv_arm 101 / 3 = 33 -2147483648 / -1 = -2147483648 qemu: uncaught target signal 8 (Floating point exception) - core dumped Floating point exception (core dumped) $ ./idiv_a15 101 / 3 = 33 -2147483648 / -1 = -2147483648 101 / 0 = 0
__aeabi_idivを呼び出すほうでは、ゼロ除算でFloating point exceptionが発生しました。A15用のSDIV命令では例外は発生せずに結果は0になりました。
INT_MIN / (-1) はオーバーフローしますが、特に例外は発生せずに結果はINT_MINのままです。
今度は明示的にCPUの種別を指定して実行してみます。
$ qemu-arm -cpu cortex-a15 ./idiv_a15 101 / 3 = 33 -2147483648 / -1 = -2147483648 101 / 0 = 0 $ qemu-arm -cpu cortex-a9 ./idiv_a15 qemu: uncaught target signal 4 (Illegal instruction) - core dumped Illegal instruction (core dumped)
CPUをcortex-a9に指定すると、SDIV命令がIllegal instructionになりました。
おまけ (x86_64の場合)
x86_64では、INT_MIN/(-1) もゼロ除算もどちらもFloating point exceptionが発生します。
$ gcc -O idiv.c $ objdump -d a.out |less
関数idivは以下のようにidiv命令を使用していました。
0000000000400524 <idiv>: 400524: 89 f8 mov %edi,%eax 400526: 89 fa mov %edi,%edx 400528: c1 fa 1f sar $0x1f,%edx 40052b: f7 fe idiv %esi 40052d: c3 retq
$ ./a.out 101 / 3 = 33 Floating point exception (core dumped)
INT_MIN / (-1)でFloating point exception
$ vi idiv.c $ cat idiv.c #include <stdio.h> #include <limits.h> int idiv(int x, int y) { return x / y; } int main() { int x0, y0; x0 = 101; y0 = 3; printf("%d / %d = %d\n", x0, y0, idiv(x0, y0)); //x0 = INT_MIN; y0 = -1; //printf("%d / %d = %d\n", x0, y0, idiv(x0, y0)); x0 = 101; y0 = 0; printf("%d / %d = %d\n", x0, y0, idiv(x0, y0)); }
INT_MIN / (-1)をコメントアウト。
$ gcc -O idiv.c $ ./a.out 101 / 3 = 33 Floating point exception (core dumped)
ゼロ除算もFloating point exception
追記: 剰余算
以下の関数がCortex-A15でどうコンパイルされるか調べました。
int imod(int x, int y) { return x % y; }
$ arm-linux-gnueabi-gcc -mcpu=cortex-a15 -O -o idiv_a15 idiv.c $ arm-linux-gnueabi-objdump -d idiv_a15 |less
関数 imodの部分は以下の通り。
00008390 <imod>: 8390: fb90 f3f1 sdiv r3, r0, r1 8394: fb03 0011 mls r0, r3, r1, r0 8398: 4770 bx lr 839a: bf00 nop
sdivで商を求めた後に、乗算と減算で剰余を求めていました。