Man page of CHIKU_WAIT(2)

システムソフトウェアとポエムの差が激しいので,高山病に注意

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のオプションを使って浮動小数点演算をソフトウェア置き換え

まず、サンプルのC言語ソースコードを見てみましょう。

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 intunsigned long long intsigned intsigned 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さんと茨の道を突っ走ることが出来て楽しかったです、ありがとうございました!煽られ駆動開発中々刺激的でした

明日の記事は明石君です。