2024年07月12日
ずっと問題なく動いていたコードが LLVM 18 から動かなくなってしまったことをきっかけに始まった調査でしたが、気が付けばけっこうな分量になりました。
SoftFloatの未定義動作バグ(1)signedのunsignedな絶対値を求める際にINT_MIN
SoftFloatの未定義動作バグ(2)RISC-VのRV64Iではunsignedの32bit即値でも64bitレジスタの上位32bitが0とは限らない
RV64Iでunsignedの32bit値が符号拡張されないで関数にレジスタ渡しされるのはどういう時か?
SoftFloatの未定義動作バグ(3)そもそも単精度浮動小数点数演算をソフトウェアエミュレーションする関数の仮引数はfloatにするべき
そして、コンパイラの最適化のバグではなく、コードにもともとあった問題が顕在化したという結論になりました。詳細は上記リンクを参照してください。
その調査の過程で LLVM 18 の驚異的な最適化を目の当たりにしました。以下のように float を uint32_t として扱い、符号ビットを比較するコードが…
続きを読む
SoftFloatの未定義動作バグ(1)signedのunsignedな絶対値を求める際にINT_MIN
SoftFloatの未定義動作バグ(2)RISC-VのRV64Iではunsignedの32bit即値でも64bitレジスタの上位32bitが0とは限らない
RV64Iでunsignedの32bit値が符号拡張されないで関数にレジスタ渡しされるのはどういう時か?
SoftFloatの未定義動作バグ(3)そもそも単精度浮動小数点数演算をソフトウェアエミュレーションする関数の仮引数はfloatにするべき
そして、コンパイラの最適化のバグではなく、コードにもともとあった問題が顕在化したという結論になりました。詳細は上記リンクを参照してください。
その調査の過程で LLVM 18 の驚異的な最適化を目の当たりにしました。以下のように float を uint32_t として扱い、符号ビットを比較するコードが…
aSign = a >> 31; bSign = b >> 31; if ( aSign != bSign ) { ... }RV64I ターゲットでは uint32_t であっても常に 64 bit に符号拡張されるという RISC-V の呼び出し規約を利用し、(64 bit の)xor を取って符号ビット(bit 31 ではなく bit 63)が 1、つまり負の数ならば符号ビットが異なるので次にジャンプ(Branch Less Than Zero; bltz)というコードが生成されました。
xor a2, a1, a0 bltz a2, 0x1cLLVM 17 までは、以下のように下位 32 bit を 31 bit 論理右シフト(Shift Right Logical Immediate Word; srliw)して比較(Branch Not Equal; bne)という素直なコードが出ていました。そのため問題のあるコードでも期待通り動作していたのです。
srliw a2, a0, 0x1f srliw a3, a1, 0x1f bne a2, a3, 0x38こんな最適化、いったいどうやったら実現できるのでしょうか?調べてみました。
続きを読む
2024年07月11日
前々回の記事では
続きを読む
これを「未定義動作バグ」と呼ぶのが適切なのかは自信がありませんが、おそらくプログラマには 「uint32_t を 31 ビット右シフトした場合、bit 0 以外は全て 0 になり、0x0 か 0x1 のどちらかに必ずなる」という暗黙の仮定があったのではないかと思われ、そうとは限らないという意味で未定義動作バグとしました。前回の記事では
コンパイララインタイム関数は C 言語仕様の範疇ではなく、完全にコンパイラの実装と結びついたもの(コード生成の一部と考えられる)なので、なんでもありなのでしょう。
ではなぜ compiler-rt の関数は大丈夫なのか?という疑問が生まれますが、どうもレジスタ渡しされてきた値を一度スタックに書き込んで符号付きに変換して扱っているから大丈夫なようです。などとごまかして終わらせてしまいましたが、ちゃんと調べました。
そもそも本来は __ltsf2 に渡ってくるのは float 型のようで、そのまま uint32_t で扱ってはいけないのかもしれません。
続きを読む
2024年07月10日
前回の記事「SoftFloatの未定義動作バグ(2)RISC-VのRV64Iではunsignedの32bit即値でも64bitレジスタの上位32bitが0とは限らない」の補足です。
RISCV RV64I では unsigned な 32 bit 値(uint32_t)であっても、常に汎用レジスタ上では符号拡張された形で扱われます。例えば f(uint32_t) 関数に f(0xc0000000) を渡した場合、第一引数が渡る a0(x10)レジスタには 0x00000000c0000000 ではなく 0xffffffffc0000000 が渡ります。Clang コンパイラもこの仕様を前提とした最適化を行っています。
ならば、符号拡張されない形で 32 bit 値が関数にレジスタ渡しされてきたならば、それはバグではないか?という疑問が生まれます。そもそもこの値はどこから来たのでしょうか?調べてみました。
続きを読む
RISCV RV64I では unsigned な 32 bit 値(uint32_t)であっても、常に汎用レジスタ上では符号拡張された形で扱われます。例えば f(uint32_t) 関数に f(0xc0000000) を渡した場合、第一引数が渡る a0(x10)レジスタには 0x00000000c0000000 ではなく 0xffffffffc0000000 が渡ります。Clang コンパイラもこの仕様を前提とした最適化を行っています。
ならば、符号拡張されない形で 32 bit 値が関数にレジスタ渡しされてきたならば、それはバグではないか?という疑問が生まれます。そもそもこの値はどこから来たのでしょうか?調べてみました。
続きを読む
2024年07月05日
前回の記事「SoftFloatの未定義動作バグ(1)signedのunsignedな絶対値を求める際にINT_MIN」の続きです。
前回の記事では肝心なことを書き忘れたのですが、libc の「fp ソフトウェアエミュレーション」とは、つまり libc でコンパイラランタイムライブラリ(GCC の libgcc や LLVM の compiler-rt)の関数を実装しているということです。FPU が無いターゲットでは、コンパイラは fp 命令の代わりにこの関数を呼び出します。
なぜこんな面倒なことをしているかというと、libgcc も compiler-rt も fenv.h を考慮せず、常に FE_TONEAREST で fp 演算を行うからだと思われます。現実的には FPU が無い CPU で丸め方式の考慮が必要になるようなプログラムを実行するケースは稀だと思いますが、NetBSD にはこだわりがあるのでしょう。(弊社もできる限り全てのサポートターゲットが標準に準拠した動作を行うコンパイラツールチェーン製品を目指しています。)
続きを読む
前回の記事では肝心なことを書き忘れたのですが、libc の「fp ソフトウェアエミュレーション」とは、つまり libc でコンパイラランタイムライブラリ(GCC の libgcc や LLVM の compiler-rt)の関数を実装しているということです。FPU が無いターゲットでは、コンパイラは fp 命令の代わりにこの関数を呼び出します。
なぜこんな面倒なことをしているかというと、libgcc も compiler-rt も fenv.h を考慮せず、常に FE_TONEAREST で fp 演算を行うからだと思われます。現実的には FPU が無い CPU で丸め方式の考慮が必要になるようなプログラムを実行するケースは稀だと思いますが、NetBSD にはこだわりがあるのでしょう。(弊社もできる限り全てのサポートターゲットが標準に準拠した動作を行うコンパイラツールチェーン製品を目指しています。)
続きを読む
2024年07月04日
NetBSD の libc は FPU を持たないターゲット向けの fp(浮動小数点数)ソフトウェアエミュレーションに John R. Hauser 氏が開発している Berkeley SoftFloat のバージョン 2a を使用しています。
http://www.jhauser.us/arithmetic/SoftFloat.html
弊社のコンパイラツールチェーン製品 exeClang は NetBSD の libc を使用しているのですが、LLVM 18 版の開発中に RISC-V の RV64 IMA(noFPU)ターゲットをいつものように商用コンパイラテストスイートにかけると、新規 fail が 1 つ発生しました。
その原因を追究して行く過程で、この SoftFloat の未定義動作(Undefined Behavior; UB)を 2 つ発見したので紹介します。今回はそのうち 1 つ目です。
続きを読む
http://www.jhauser.us/arithmetic/SoftFloat.html
弊社のコンパイラツールチェーン製品 exeClang は NetBSD の libc を使用しているのですが、LLVM 18 版の開発中に RISC-V の RV64 IMA(noFPU)ターゲットをいつものように商用コンパイラテストスイートにかけると、新規 fail が 1 つ発生しました。
その原因を追究して行く過程で、この SoftFloat の未定義動作(Undefined Behavior; UB)を 2 つ発見したので紹介します。今回はそのうち 1 つ目です。
続きを読む
2024年06月27日
MSYS2 を使用する場合、MinGW64 環境と UCRT64 環境の 2 つがあります。
MinGW64 は馴染み深い Microsoft Visual C++ の msvcrt を C RunTime(CRT)ライブラリに使用します。一方 UCRT64 は Windows 10 から使用可能な Universal CRT を使用します。(UCRT64 は 2020 年ごろから開発が始まった比較的新しい環境です。)
今まではどちらも同じようなものだと思っていたので惰性で MinGW64 を使い続けてきましたが、同じバージョンの QEMU が UCRT 版(mingw-w64-ucrt-x86_64-qemu)は期待通り動くのに、MinGW64 版(mingw-w64-x86_64-qemu)は動かないケースがありました。今後は特別な理由が無い限り UCRT64 環境を使おうと思います。
詳細は以下になります。
続きを読む
MinGW64 は馴染み深い Microsoft Visual C++ の msvcrt を C RunTime(CRT)ライブラリに使用します。一方 UCRT64 は Windows 10 から使用可能な Universal CRT を使用します。(UCRT64 は 2020 年ごろから開発が始まった比較的新しい環境です。)
今まではどちらも同じようなものだと思っていたので惰性で MinGW64 を使い続けてきましたが、同じバージョンの QEMU が UCRT 版(mingw-w64-ucrt-x86_64-qemu)は期待通り動くのに、MinGW64 版(mingw-w64-x86_64-qemu)は動かないケースがありました。今後は特別な理由が無い限り UCRT64 環境を使おうと思います。
詳細は以下になります。
続きを読む
2023年12月06日
従来は Linux や Apple などのリッチ OS のアプリ向けというイメージだった LLVM の高速リンカ lld ですが、LLVM 17 で GNU ld との互換性がほぼ完璧になり、AArch64/ARM/RISC-V のベアメタルツールチェーンでも GNU ld を置き換えできることが確認できました。そこで弊社の SOLID もリンクの高速化や Clang での LTO などを期待して lld 対応を進めているのですが、その時に 1 点だけ非常にわかりにくい非互換性に悩まされたのでメモしておきます。
続きを読む
続きを読む
2023年10月26日
2023年04月28日
私は担当ではないので詳しくないのですが、弊社では Rust の開発環境を提供しています。
https://www.kmckk.co.jp/pdf/20211020_solid_press.pdf
それとは特に関係なく、諸事情で Rust で書かれたライブラリをベアメタル環境で使う必要が出てきたので、遅ればせながら Rust の勉強を始めました。
まずはこのチュートリアルをやってみた所、予想外にいろいろハマってしまい、情報も少なくて困ったので、うまくいった方法をメモしておきます。(私も初心者なので、このやり方が正しいとは限りません。)
AArch64 Bare-Metal program in Rust
https://lowenware.com/blog/aarch64-bare-metal-program-in-rust/
続きを読む
https://www.kmckk.co.jp/pdf/20211020_solid_press.pdf
それとは特に関係なく、諸事情で Rust で書かれたライブラリをベアメタル環境で使う必要が出てきたので、遅ればせながら Rust の勉強を始めました。
まずはこのチュートリアルをやってみた所、予想外にいろいろハマってしまい、情報も少なくて困ったので、うまくいった方法をメモしておきます。(私も初心者なので、このやり方が正しいとは限りません。)
AArch64 Bare-Metal program in Rust
https://lowenware.com/blog/aarch64-bare-metal-program-in-rust/
続きを読む
2023年04月26日
このブログでも何度か取り上げてきましたが、QEMU の Windows バイナリ(x64)のビルドは、以前はとても大変でした。今回、とある事情で数年ぶりに Xilinx QEMU の git HEAD(QEMU 7.1.0 ベース)をビルドする必要がでてきたので調査したところ、以前作った Xilinx QEMU 2020.3(QEMU 5.0.50 ベース)のビルド環境ではライブラリが古いためビルドできず、アップデートも困難な状態でした。
最終的に、最も王道である MSYS2 でビルドが成功し、正常動作が確認できました。その時のメモです。
続きを読む
最終的に、最も王道である MSYS2 でビルドが成功し、正常動作が確認できました。その時のメモです。
続きを読む
2022年07月28日
特定の OS を前提としないベアメタルのツールチェーン(いわゆる aarch64-unknown-elf のようなターゲット)に付属するライブラリは、マルチスレッド関係のライブラリの排他制御などが全て OFF になった状態です。pthread などのスレッドライブラリを前提にすることは当然できませんが、Thread Local Storage(TLS)だけならば OS に依存しない形で実装でき、かつ OS を使う場合は無変更でライブラリ関数のスレッドセーフ化が可能なのではないか?と思いつき、調査した時のメモです。
続きを読む
続きを読む
2021年06月14日
2021年04月14日
GCC の場合、ホストの C++ コンパイラのみ(通常は GCC)で、Binutils と GCC のソースコードから完全な C/C++ クロスコンパイラをビルドすることが可能です。
これまで Clang は GCC に依存しており、LLVM プロジェクトのソースコードのみで完全なクロスコンパイラをビルドすることはできない(そのため、GCC のヘッダやライブラリをそのまま使うしかない)という認識でした。この誤解は、CMake が完全な(a.out を生成可能な)C/C++ コンパイラを要求するので、Clang のランタイムライブラリである compiler-rt を Clang 自身でビルドする方法がわからなかったためです。一つでも GCC でライブラリをビルドしてしまうと、そのライブラリは GCC のヘッダに依存することになるので、他のライブラリも全て同じ GCC でビルドしなければなりません。
しかし最近いろいろ調べていて、実はそれが可能であることがわかりました。
ただし、以下の制限があります。(この記事では RISC-V をターゲットとします。他のターゲットでは以下の制限は無い可能性があります。)
続きを読む
これまで Clang は GCC に依存しており、LLVM プロジェクトのソースコードのみで完全なクロスコンパイラをビルドすることはできない(そのため、GCC のヘッダやライブラリをそのまま使うしかない)という認識でした。この誤解は、CMake が完全な(a.out を生成可能な)C/C++ コンパイラを要求するので、Clang のランタイムライブラリである compiler-rt を Clang 自身でビルドする方法がわからなかったためです。一つでも GCC でライブラリをビルドしてしまうと、そのライブラリは GCC のヘッダに依存することになるので、他のライブラリも全て同じ GCC でビルドしなければなりません。
しかし最近いろいろ調べていて、実はそれが可能であることがわかりました。
ただし、以下の制限があります。(この記事では RISC-V をターゲットとします。他のターゲットでは以下の制限は無い可能性があります。)
- LLVM のリンカ LLD は RISC-V のデフォルトである -mrelax オプション(Linker optimization/relaxation)のサポートが完全ではないなど、様々な問題があるため、リンカのみ Binutils の GNU ld を使用します。
- LLVM の C++ ランタイム実装 libc++abi と C++ ライブラリ libc++ が newlib ではビルドできないようなので、今回は C コンパイラのみビルドします。(libc++abi の実装に使用される C++ 例外の実装 libunwind はビルド可能ですが、C の場合は不要なので今回は割愛します。)
- RISC-V 命令セットシミュレータの spike と一緒に使用する pk カーネルが clang ではビルドできないようなので、spike/pk は今回はビルドせず、以前の環境で GCC を使ってビルドしたものを使用します。(OVPsim の無償版は現在 V 拡張をサポートしていない問題があります。)
続きを読む
2021年02月19日
Binutils や GCC は、Ubuntu 上で Windows で動作する、組み込み向け(RISC-V など)のコンパイラツールチェーンを簡単にビルドできます(カナディアンクロスビルド)。configure の際に --target=riscv64-unknown-elf --host=x86_64-w64-mingw32 --build=x86_64-pc-linux-gnu を指定するだけで、後は基本的にネイティブと同じです。この時、Ubuntu 上(x86_64-pc-linux-gnu)で動作するクロスの x86_64-w64-mingw32-gcc(g++)でビルドが行われるという仕組みです。
Clang の場合、CMake でどのように設定すれば良いのかとか、Windows バイナリを生成可能な clang が Ubuntu 上に見当たらなかった(clang をビルドするには、MinGW gcc ではなく、clang が必要)などの理由により、これまでは VisualStudio を使って Windows 上でビルドしていました。
今回、以下の記事で llvm-mingw の存在を知り、無事カナディアンクロスビルドに成功しました。
続きを読む
Clang の場合、CMake でどのように設定すれば良いのかとか、Windows バイナリを生成可能な clang が Ubuntu 上に見当たらなかった(clang をビルドするには、MinGW gcc ではなく、clang が必要)などの理由により、これまでは VisualStudio を使って Windows 上でビルドしていました。
今回、以下の記事で llvm-mingw の存在を知り、無事カナディアンクロスビルドに成功しました。
続きを読む
2021年02月05日
職場の開発 PC が AMD Ryzen 9 5950X(16 コア 32 スレッド)RAM 32GB になったのですが、これまで 1 時間以上かかっていたビルドが数分で終わるようになったので劇的に効率が上がりました。Release ビルドならば 16 並列で CPU ほぼ 100% 使い切りメモリも足りているようです。Debug ビルドは 8 並列ぐらいまで落としてもメモリ不足で失敗しますが、何回か繰り返すと最後まで終わるので、デバッグ時なら許容範囲かなという感じです。LLVM の開発をするならば、できれば RAM は 64GB 欲しい所ですね。
新規にビルド環境を作ったので、その時のメモです。
続きを読む
新規にビルド環境を作ったので、その時のメモです。
続きを読む