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



トラックバックURL

コメントする

名前
URL
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード