Berkeley SoftFloatを使って浮動小数点数演算をソフトウェアエミュレートで置き換える(x86_64)
はじめに
この記事は公立はこだて未来大学 Advent Calendar 2017 18日の記事です。 adventar.org
今更ながら、夏のインターンでやってたことをまとめます。KLabの方々遅くなってすみません...。
さて、夏のインターンですが、mrubyの浮動小数点数演算をソフトウェアエミュレートで実行するという内容をしてました。 浮動小数点数演算にはハードウェアでの実行、ソフトウェアでの実現の2種類があります。 普段プログラミングしている際は気にすること無く浮動小数点数を扱いますが、レイヤーによってはハードウェアでの浮動小数点演算に制限がある場合などがあります。例えば、Linux Kernel内では浮動小数点演算を行ってはならないという制約がありますね。
僕がやっている mruby in BitVisorも同様に浮動小数点演算に制限があります(自分でレジスタの値を退避されるコードを書けば問題ない)。そこでソフトウェアフロートで置き換えることをしました。この記事ではまず、Berkeley SoftFloatの解説と使い方について書こうと思います。mrubyの置き換えに関してはmruby Advent Calendar 2017 - Qiitaをお待ち下さい。 Berkeley SoftFloatのサンプルコードや日本語の記事は殆どないので何か参考になればと思います。
まあx86_64でソフトウェアフロート使いたがる奇特な人なんてほとんど居ないと思うけど
記事を書くに当たっての環境
x86_64でgccのオプションを使って浮動小数点演算をソフトウェア置き換え
int main(int argc, char *argv[]) { float a = 10; float b = 20; float c = a + b; return 0; }
このコードをgcc -S test.c
でアセンブラコードの出力をしてみましょう。
.file "test.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -20(%rbp) movq %rsi, -32(%rbp) movss .LC0(%rip), %xmm0 movss %xmm0, -12(%rbp) movss .LC1(%rip), %xmm0 movss %xmm0, -8(%rbp) movss -12(%rbp), %xmm0 addss -8(%rbp), %xmm0 movss %xmm0, -4(%rbp) movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .section .rodata .align 4 .LC0: .long 1092616192 .align 4 .LC1: .long 1101004800 .ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609" .section .note.GNU-stack,"",@progbits
movss %xmm0, -12(%rbp)
などの部分でSSE(Streaming SIMD Extensions)にて使用するxmm0という128bitレジスタを使ってますね。また、movssは32ビット単精度浮動小数点数のコピーを行います。このように浮動小数点数を扱う命令セットやレジスタ使用されているので、これらを使用しないようにしましょう。
gcc test.c -mno-sse -mno-sse2 -mno-sse3 -mno-mmx -msoftfloat
でコンパイルしてみます。 -mno-○○でSSE関係を無効化し、-msoftfloatでソフトウェアによる浮動小数点のサポートを有効化します。これで問題無さそうに思えますが、これはerror : SSE register return with SSE disabled
とエラーとなってしまいます。調べてみるとどうやら、 gccはすべてのx86-64プロセッサにSSEがあると仮定しているから上手く行かないみたいです。
Berkeley SoftFloatを使って浮動小数点数演算を置き換える
さて、頼みの綱のgccで上手く行かないことがわかりましたが、どうしてもx86_64でソフトウェアで浮動小数点演算を行ないたい…!というわけでgccがやってくれないならgccの代わりに自分で手を動かして置き換えるしかなさそうです。といっても、フルスクラッチでやると死んでしまうので世の中には便利なライブラリがあったりします。今回はBerkeley SoftFloatを使って置き換えてみます。
Berkeley SoftFloatとは
カリフォルニア州立大学バークレー校の方が作った浮動小数点数の計算にて広く採用されている標準規格のIEEE754に準拠にした比較的高速な浮動小数点演算のソフトウェア実装のライブラリです。現在の最新バージョンでは半精度(16bit)、単精度(32bit)、倍精度(64bit)、拡張倍精度浮動(80bit)、四倍精度(128bit)のフォーマットに対応しています。また、全てのフォーマットに対して端数処理や特殊の値をサポートしています。
Berkeley SoftFloatの使い方
まず、Berkeley SoftFloatをgithubからクローンします。git clone https://github.com/ucb-bar/berkeley-softfloat-3.git
その後、cd berkeley-softfloat-3/build/Linux-x86_64-gcc/
してmake
します。
変数宣言
まず、floatの置き換えはsoftfloat_types.hに定義されているfloat32_tで置き換えます。double型の場合はfloat64_t
を、long double型の場合はextFloat80_t
を使用します。これらの型はsoftfloat_types.hで構造体として定義されています。これらの型はunsigned intで値を16進数で保持します。
typedef struct { uint16_t v; } float16_t; typedef struct { uint32_t v; } float32_t; typedef struct { uint64_t v; } float64_t; typedef struct { uint64_t v[2]; } float128_t; #ifdef LITTLEENDIAN struct extFloat80M { uint64_t signif; uint16_t signExp; }; #else struct extFloat80M { uint16_t signExp; uint64_t signif; }; #endif
変数の初期化と整数・浮動小数点数の変換
これらの変数の初期化は float32_t hoge = {0x3F800000}
のように16進数で初期化するか、i32_to_f32()
のような関数を用いて整数→float32_tに変換するような関数を用います。これらの関数はsoftfloat.hに定義されています。
float16_t ui32_to_f16( uint32_t ); float32_t ui32_to_f32( uint32_t ); float64_t ui32_to_f64( uint32_t ); float16_t ui64_to_f16( uint64_t ); float32_t ui64_to_f32( uint64_t ); float64_t ui64_to_f64( uint64_t ); float16_t i32_to_f16( int32_t ); float32_t i32_to_f32( int32_t ); float64_t i32_to_f64( int32_t ); float16_t i64_to_f16( int64_t ); float32_t i64_to_f32( int64_t ); float64_t i64_to_f64( int64_t );
softfloat.hに定義されているように、unsigned long int
、unsigned long long int
、signed int
、signed long long int
から 各種フォーマットへの変換が可能です。同様に各種フォーマットから整数への変換をする関数も用意されています。
extern THREAD_LOCAL uint_fast8_t softfloat_roundingMode; enum { softfloat_round_near_even = 0, softfloat_round_minMag = 1, softfloat_round_min = 2, softfloat_round_max = 3, softfloat_round_near_maxMag = 4, softfloat_round_odd = 5 }; … uint_fast32_t f16_to_ui32( float16_t, uint_fast8_t, bool ); uint_fast64_t f16_to_ui64( float16_t, uint_fast8_t, bool ); int_fast32_t f16_to_i32( float16_t, uint_fast8_t, bool ); int_fast64_t f16_to_i64( float16_t, uint_fast8_t, bool );
例えば、これは単精度浮動小数点数から整数への変換の関数の一覧です。第一引数のfloat16_tには変換したい変数、第二引数のuint_fast8_tには端数処理を指定するためにsoftfloat_roundingModeを指定します。最後に第三引数のboolはIEEE754の浮動小数点例外の不正確例外を発生するかどうかを指定することができます。このboolを真にした場合は変換が不正確な場合、不正確例外が発生します。それ以外では不正確な場合でも例外が発生しません。
四則演算
このライブラリでは四則演算と剰余、平方根がサポートされています。
float32_t f32_add( float32_t, float32_t ); float32_t f32_sub( float32_t, float32_t ); float32_t f32_mul( float32_t, float32_t ); float32_t f32_div( float32_t, float32_t ); float32_t f32_rem( float32_t, float32_t ); float32_t f32_sqrt( float32_t );
単精度浮動小数点数の場合これらは名前の通りf32_addは加算、f32_subは減算、f32_mulは乗算、f32_divは除算、f32_remは剰余、f32_sqrtは平方根を示します。これらの関数は同じ浮動小数点数フォーマット同士の計算を行います。そのため、違うフォーマット同士や整数との計算を行う時はフォーマットを合わせる必要があります。例えば、整数と単精度浮動小数点数の加算を行う場合はfloat32_t a = f32_add(b,i32_to_f32(c))
のようにします。
置き換え後
Berkeley SoftFloatで置き換えたサンプルです。
#include <softfloat.h> int main(int argc, char *argv[]) { float32_t a = i64_to_f32(10); float32_t b = i64_to_f32(20); float_32t c = f64_add(a, b); return 0; }
ではこのコードのアセンブラコードを出力をしてみましょう。
.file "test_soft.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $48, %rsp movl $10, %edi call i64_to_f32 movl %eax, -48(%rbp) movl $20, %edi call i64_to_f32 movl %eax, -32(%rbp) movl -32(%rbp), %edx movl -48(%rbp), %eax movl %edx, %esi movl %eax, %edi call f32_add movl %eax, -16(%rbp) movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609" .section .note.GNU-stack,"",@progbits
はい、浮動小数点数を扱う命令セットやレジスタが使われなくなりましたね。これでx86_64でもハードウェアを使わずに浮動小数点演算ができました。これで浮動小数点演算の実行が制限されている場合でも扱えるようになりますね。他のアーキテクチャの場合はgccのオプションだけで解決できるはずです。
おわりに
このライブラリを用いてインターンでは浮動小数点演算の置き換えを実際にしました。なかなかドキュメントがなく最初はメンターのPandaxさんと使い方に苦労しました。本当は使い方の記事はインターン終わってすぐ投稿するつもりだったんですが、遅くなりました…。改めてインターンではPandaxさんと茨の道を突っ走ることが出来て楽しかったです、ありがとうございました!煽られ駆動開発中々刺激的でした
明日の記事は明石君です。