2012年01月26日
SHのgccでの浮動小数点演算のオプションとその生成コード
SHのgccでは浮動小数点演算のオプションの指定方法が他のアーキテクチャと異なっています。
それぞれどんなコードが生成されるのか確認してみました。
CPUの指定と浮動小数点演算のモードの指定
SH以外のアーキテクチャではCPUの種別の指定と浮動小数点演算のモードは別々のオプションを指定しますが、SHの場合はこれがひとつになっています。
CPUの種別がSH4Aの場合、以下の4通りのオプションが選択できます。
- -m4a
- -m4a-single
- -m4a-single-only
- -m4a-nofpu
一番下の -m4a-nofpuでは浮動小数点演算にFPUを使用せず、ソフトウェアで演算するライブラリを呼び出します。
SH以外のCPUではdouble(倍精度)の演算とfloat(単精度)の演算では別々の命令があるのが普通ですが、SHではこれらは同一の命令で、精度の種別はFPUのステータスレジスタのビットによって指定します。おそらくSHは一命令16bitに統一しているので、オペコードの中に精度を区別するビットを確保できなかったのでしょう。
つまり、SHでは浮動小数点演算は(1)精度の指定 (2)演算の2段階で行うことになります。しかし倍精度eと単精度を交互に使う場合でない限りは精度の指定は省略することができます。そこで、-m4aではデフォルト状態を倍精度とし、倍精度の演算の場合には精度の指定を省略し、単精度の演算のときには(1)精度を単精度に設定 (2)演算 (3)精度を倍精度に戻す ということを行います。 -m4a-singleは逆にデフォルト状態を単精度にします。
また、-m4a-single-onlyは特殊なオプションで、ソースコード上でdouble(倍精度)であっても、それをfloat(単精度)に読み替えてコードを生成します。このため、全て単精度に統一するために精度を切り替えるオーバヘッドをなくすことができます。しかし、doubleが8バイトあることを決めうちにしていたり、中のbitを直接扱ったりするプログラムは正しく動作しなくなります。
当然のことながら、同じプログラムの中でこれらのオプションは統一しておく必要があります。混在することはできません。
実際に出力されたコードを見る
float.c
double dadd(double x, double y) { return x + y; } float fadd(float x, float y) { return x + y; }
このプログラムを4種類のオプションでコンパイルしてみます。
(以下、本質的でない行は削除してあります。元のファイルはこちらをみてください。)
sh-kmc-elf-gcc -O2 -S -m4a float.c
_dadd: fmov fr4,fr0 fmov fr5,fr1 mov.l r14,@-r15 mov r15,r14 fadd dr6,dr0 mov r14,r15 rts mov.l @r15+,r14 _fadd: mov.l .L5,r1 mov.l r14,@-r15 lds.l @r1+,fpscr mov.l .L6,r1 mov r15,r14 fmov fr5,fr0 fadd fr4,fr0 lds.l @r1+,fpscr mov r14,r15 rts mov.l @r15+,r14 .L7: .align 2 .L5: .long ___fpscr_values .L6: .long ___fpscr_values+4
SHは遅延分岐するので、rtsの実行前にその次の命令を実行します。
daddとfaddではfaddのときにfpscrレジスタの設定が追加されてコードが長くなっています。___fpscr_valuesと___fpscr_values+4の初期値はスタートアップルーチンでセットされています。
sh-kmc-elf-gcc -O2 -S -m4a-single float.c
_dadd: mov.l .L3,r1 mov.l r14,@-r15 lds.l @r1+,fpscr fmov fr4,fr0 mov r15,r14 fmov fr5,fr1 mov.l .L4,r1 fadd dr6,dr0 lds.l @r1+,fpscr mov r14,r15 rts mov.l @r15+,r14 .L5: .align 2 .L3: .long ___fpscr_values .L4: .long ___fpscr_values+4 _fadd: fmov fr5,fr0 fadd fr4,fr0 mov.l r14,@-r15 mov r15,r14 mov r14,r15 rts mov.l @r15+,r14
さきほどとは逆にfaddはすっきりしていますが、daddは長いコードです。
sh-kmc-elf-gcc -O2 -S -m4a-single-only float.c
_dadd: fmov fr4,fr0 fadd fr5,fr0 mov.l r14,@-r15 mov r15,r14 mov r14,r15 rts mov.l @r15+,r14 _fadd: fmov fr4,fr0 fadd fr5,fr0 mov.l r14,@-r15 mov r15,r14 mov r14,r15 rts mov.l @r15+,r14
daddもfaddもどちらも同じコードになっています。
sh-kmc-elf-gcc -O2 -S -m4a-nofpu float.c
_dadd: mov.l r14,@-r15 mov.l .L3,r0 sts.l pr,@-r15 jsr @r0 mov r15,r14 mov r14,r15 lds.l @r15+,pr rts mov.l @r15+,r14 .L4: .align 2 .L3: .long ___adddf3 .size _dadd, .-_dadd .global ___addsf3 _fadd: mov.l r14,@-r15 mov.l .L7,r0 sts.l pr,@-r15 jsr @r0 mov r15,r14 mov r14,r15 lds.l @r15+,pr rts mov.l @r15+,r14 .L8: .align 2 .L7: .long ___addsf3
FPU命令の代わりに___adddf3, ___addsf3というライブラリ関数を呼んでいます。
余談
SHはデフォルトでIEEEへの準拠の度合いが低いです。もともとが制御用マイコンから発展したせいでしょうか。他のアーキテクチャと同等レベルのIEEE準拠にするには -mieeeをつける必要があります。数学関数ライブラリなどをコンパイルするときには要注意です。
GCCのマニュアル SH-Options