GHCいじくり日誌・AArch64編

ここ数日またGHCをいじっている。少し前にAArch64 NCGがマージされたのでその確認という意味が大きい。

前回の記事(3月):GHCに初めてコントリビュートした/最近のGHC動向

同ジャンルの記事(2020年10月):GHCデバッグ日誌

abs関数とNaNの符号ビット

自作ライブラリーfp-ieeeのテストを(Gitから取ってきた)最新のGHCで動かしてみたら引っかかるものがあった。

どうやら、筆者のコードが「Float/Doubleに関するabs関数が符号ビットを常にクリアする」ことを期待しているのに対し、AArch64 NCGを使った場合はNaNの符号ビットがクリアされないようだ。

数日前に、絶対値関数の実装についての話がツイッターに流れてきた:

話題になった記事では「ゼロの符号を考慮しろ」という話をしているようだが、筆者が遭遇したのはさらに一歩先、「NaNの符号ビット」の問題である。

abs (-0) の問題はGHCは7年前に通過している:abs for IEEE floating point is slightly wrong. (#8780) · Issues · Glasgow Haskell Compiler / GHC · GitLab GHC 7.8の頃に報告されてGHC 7.10で修正されたらしい)

他の浮動小数点数と同様、NaNにも符号ビットの概念がある。NaNの符号ビットは通常の比較演算では考慮されないが、IEEE 754で定められたcopySignやisSignMinus演算はNaNから符号ビットを取り出すことを可能にする。そして、IEEE 754準拠のabs演算はNaNの符号ビットをクリアしなければならない。

筆者のライブラリーではFloat, Doubleに関する標準のabs関数がIEEE 754準拠であることを期待していたが、実はそうではなかったのだ。ただGHCのx86 NCGやLLVMバックエンドではたまたま符号ビットをクリアする実装になっていただけで、SPARC NCGとかvia-CバックエンドではNaNの符号ビットはクリアされない。

で、筆者としてはabs関数がなるべくIEEE 754準拠であった方が嬉しいのでAArch64 NCGに関して修正を出すことにした。

この修正ではabs関数(が内部で使っているGHCプリミティブ)がAArch64のFABS命令に落ちるようになる。筆者がNCGのコードを触るのは初めてだったが、見様見真似でなんとかなった。

ちなみに、sqrt関数にも対応する命令FSQRTがある。sqrtについて関数呼び出しを省いて直接FSQRT命令を呼び出すようにする改修も可能だが、今回は放っておいた。

GHCiの動作がApple Silicon Mac上でだけおかしい

GHCiではEmacsライクなキーバインディングをサポートしていて、C-aで行頭に戻ったりできる(設定次第でViライクなキーバインディングも使えるらしいが筆者はよく知らない)。

が、Apple Silicon Mac上で試すとC-aで表示上は1文字しか戻らないように見える。内部的には行頭に戻っているようなので不整合が起こる。

AArch64特有の問題かと思ってRaspberry Piに入れたUbuntu上で試すと問題ない。Intel Macでも問題ない。ということはAArch64かつMacの場合のみ発生する問題ということだ。

GHCiの行編集機能はhaskelineというライブラリー(readlineのHaskell版)を使っている。haskelineのサンプルプログラムを試すと問題が再現したのでこれはhaskelineの問題である。とはいえ、haskelineにそんなにアーキテクチャー依存なコードが含まれるものだろうか?

実は、AArch64上でのmacOSのCの呼び出し規約は他とちょっと違う。なのでC FFI周りで、同じAArch64であっても他のOSで問題なく動くコードがmacOSでは動かない、という状況は起こりうる。7月のGHC blogの記事でもその辺に触れている:

対処としてはOSのAPI(特に可変長引数のもの)を呼ぶ際にccallではなくcapi(擬似)呼び出し規約を使う。

というわけで、haskelineが使っているC FFIを検索してみたが、WindowsはともかくUnix系では2箇所しか使われていない。うち1箇所(ioctl)はすでにcapi呼び出し規約を使うように改修済みで、もう1箇所(haskeline_mk_wcwidth)は問題があるようには見えない。

困ったが、ビルド時にterminfoフラグを切ると問題が発生しなくなることがわかった。terminfoフラグはデフォルトで有効で、terminfoライブラリーを使うようになる。terminfoにはC FFIがたくさん含まれており、その中に原因がある可能性は高い。

terminfoのプルリクエストを見てみると、それっぽいプルリクエストがあった:

これと同じ内容を手元で適用したら問題は解決した。

と、ここまでやって改めてhaskelineのissueを確認すると

というものがあった。これはまさに筆者が悩んでいた問題ではないか。なんでClosedになっているんだ。

このissueには「terminfoフラグを切る」以外の対処方法も載っていて、それはTERM環境変数をdumbに設定するというものだ。だから

$ TERM=dumb ghci

という風にghciを起動すればApple Silicon上での問題を回避できる。

一応issueにコメントしておいたので早く解消されて欲しい。

head.hackage

さっき「自作のライブラリーをGitから取ってきたGHCで動かしてみた」と書いたが、安定版と比べて最新のGHCには破壊的更新がいくつか(Data.Listの件とかInt<N>#, Word<N>#とか)含まれているのでそのままではビルドできない。個々の修正は比較的単純だが、数が多いと大変である。

そこで、最新のGHCで各種ライブラリーを使うための(ライブラリーに対する)パッチ集というのがある。それがhead.hackageである。

使い方は、cabal.projectに “How to use” にある内容を貼り付ければ良い。

その他話題

textパッケージが内部UTF-8になる

ここ数日のHaskell界隈でアツい話題といえば、textパッケージの内部UTF-8化だろう。

textパッケージはUnicode文字列を扱う標準的なパッケージである。ここでの「Unicode文字列」というのは具体的にはUnicodeスカラー値の列であり、外から使う分には内部表現(UTF-ナントカ)は気にしなくて良いようになっている。

その内部表現がこれまではUTF-16(文字列が16ビット整数列でしかないどこぞの言語と違って、正当なUTF-16であることがデータ型の不変条件として保証されている)だったのが、UTF-8に変えようという提案が5月ぐらいにあった:

それがこの度実装されたということのようである。

筆者的に驚いた点は、UTF-8のバリデーションを極限まで高速化するためにsimdutfというC++のライブラリーから1万4千行のC++コードを採用している点である(こいつはx86以外のSIMD命令にも対応しているようだ)。Cによる代替(SIMDはどうすんの?)も開発中ということだが、ひたすらすごいとしか言えない。

ちなみに、純粋なCで書かれた部分にもAVX512等の拡張命令を選択的に使う部分があったりしてすごい(ボキャ貧)。

宣伝

11月7日にHaskell Dayというオンラインイベントがあります。

筆者はそこで「GHCの動向2021」と題して話すことになりました。動画の提出締め切りが9月後半なので最新情報をどこまで盛り込めるか不確定なのですが、なんとかがんばります。

よろしければカレンダーに登録しておいてください。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です