事実上の標準なHaskell処理系であるGHCに貢献するというのが去年掲げた目標だったが、それがようやく実現したので報告する。ついでに、最近のGHC開発状況についても簡単にまとめてみる。
「貢献」と言っても色々あって、バグ報告とかも立派な貢献なのだが、ここで目標としていたのは「書いたコードをGHC本体に取り込んでもらう」ことである。
目次
一つ目:fromInteger :: Integer -> {Float,Double}
年末に書いた記事
にあるように、現行のGHCのfromIntegerは値の大きさによって丸め方法が違っている。それによってどういう問題が引き起こされるかというと、
import Numeric import Data.Word main = do putStrLn $ "literal : " ++ showHFloat (0xFFFFFFFFFFFFFC00 :: Double) "" putStrLn $ "fromInteger : " ++ showHFloat (fromInteger 0xFFFFFFFFFFFFFC00 :: Double) "" putStrLn $ "fromRational : " ++ showHFloat (fromRational 0xFFFFFFFFFFFFFC00 :: Double) "" putStrLn $ "fromIntegral/Word64 : " ++ showHFloat (fromIntegral (0xFFFFFFFFFFFFFC00 :: Word64) :: Double) ""
というコードを書いた際に
最適化なしの出力:
literal : 0x1p64 fromInteger : 0x1.fffffffffffffp63 fromRational : 0x1p64 fromIntegral/Word64 : 0x1.fffffffffffffp63
最適化ありの出力:
literal : 0x1p64 fromInteger : 0x1.fffffffffffffp63 fromRational : 0x1p64 fromIntegral/Word64 : 0x1p64
という風に
- リテラルとfromIntegerで結果が食い違う
- fromIntegerとfromRationalで結果が食い違う
- fromIntegralの結果が最適化の有無で変わる
という事態になっていた。
なので、fromIntegerの実装を修正した。
このMRを出したのは1月だったが、昨日ぐらいにようやくmergeされた。上手くいけばGHC 9.2に反映される。
二つ目:AArch64での呼び出し規約
昔書いた
にあるように、GHCは内部的に独自の呼び出し規約を使っている。詳しいことは
を見て欲しいのだが、よくある「最初の数個はレジスター渡し、残りはスタック渡し」という感じになっている。
で、MachRegs.hを見る限りAArch64では単精度に4つ、倍精度に4つのレジスターをそれぞれ使うように見える……のだが、実際はそうなっておらず、倍精度の3つ目以降の引数・返り値はスタックで渡されるようになっていた。
まあGHCを普通に使う分にはこんなこと気にしなくて良いし、それこそアセンブリー直書きでforeign import primを実装でもしない限り発覚しなかったのだが、筆者がアセンブリー直書きを試みたために白日の下へ晒されることとなった。
この問題は1年くらい前に筆者が報告し、
最近筆者が投げたMRで修正された。
修正自体はほんの数行で済むのにこんなに時間がかかったのは、テストケースを用意するのが面倒だったからである。これもGHC 9.2で反映される見込みだ。
こっちは2月にMRを出してから割とすぐにapproveされてマージされた。なので筆者にとって初めてmergeされたのはこのMRとなる。
GHCのマージプロセス
GHCに投げたMRが取り込まれるには
- レビューを経てapproveされる
- CIが通る
の2つが必要だ。まあ大きめのプロジェクトならどこでもそうだと思う。ただ、GHCの場合は特に2番目(CI)がなかなか厄介で、謎の理由で失敗したりする。パフォーマンス特性が変わる?とアウトっぽい。
時間のかかるCIがランダムに失敗するとかなり虚無なので、開発者陣の間でも問題視されている(The ghc-devs March 2021 Archive by thread の「On CI」というスレッド)。
まあそれは置いておいて、MRがapproveされてCIが通ったとしよう。普通のプロジェクトならそのままマージされるのだろうが、GHCの場合は一旦Marge Bot預かりになる。GHCのMR一覧
を見ると上の方に「Marge Bot Batch MR – DO NOT TOUCH」というのが見えるはずだ。
筆者は詳しい事情は知らないが、Marge Botというのは複数のMRに対して一括でCIを回し、マージ作業を行うbotのようだ。Marge Botにマージされた場合は元のMRは(Mergedではなく)Closed扱いになるっぽい。
最近のGHC動向
そんなこんなでしばらくGHCのIssuesとかMerge Requestsを眺める日々が続いたので、最近のGHC開発状況についても書いてみる。
Apple Silicon対応
現段階(2021年3月19日)ではGHCはApple Siliconにネイティブ対応していない。ただ見通しは立っている。
この話題については最近Ben Gamari氏(GHCの主要開発者の一人)がブログに書いている
のでぶっちゃけそちらを参照して欲しいのだが、要約すると
- GHC 8.10系:近日リリースされるGHC 8.10.5で対応予定(LLVMバックエンドのみ)。
- GHC 9.0系:近日リリースされるGHC 9.0.2 (もしくは 9.0.3)で対応予定(LLVMバックエンドのみ)。
- GHC 9.2系:LLVMバックエンドのほか、GHC本体にネイティブコード生成器 (NCG) が実装される予定。
となる。関連Issue・MRはこの辺:
- Support for Apple ARM hardware (#18664) · Issues · Glasgow Haskell Compiler / GHC · GitLab
- 一応tracking issue…のようなツラをしているが最新のロードマップは前述ブログ記事や後述AArch64 NCGのMRを見ないとわからない。
- WIP: AArch64 NCG (!3641) · Merge Requests · Glasgow Haskell Compiler / GHC · GitLab
- LLVMバックエンドにはコード生成が遅いという問題がある。ただでさえ遅いことで有名なGHCが劇遅となってしまう。
- 筆者がIntel Mac (2020)とApple M1搭載MacのそれぞれでGHCをネイティブビルドしたところ、後者の方が明らかに遅かった。マシンの性能差がそんなにあるとは思えないので、これは後者がLLVMを使っているせいだと思われる。(検証のためには前者もLLVMでビルドしたりする必要があるのかもしれないが面倒なのでやらない)
- なので、「LLVMほど最適化を頑張らない代わりに、そこそこコード生成が早い」ネイティブコード生成器 (NCG) の需要がある。
- GHC 9.2に向けて開発が進められている。
- llvmGen: Accept range of LLVM versions (!5211) · Merge Requests · Glasgow Haskell Compiler / GHC · GitLab
- 現在のGHCでは一つのバージョンが対応するLLVMのメジャーバージョンは一つだけ(例:GHC 8.10.4はLLVM 9.0のみ対応。LLVM 8でもLLVM 10でもダメ)なのだがそれでは古いGHCをApple Siliconに対応させる上で不都合があるため、この制限は緩和されるようだ。
- 多分これがghc-8.10にbackportされたら8.10.5がリリースされる…気がする。
GHC 9.0系
2月にGHC 9.0.1がリリースされた。結構難産だったようだ。
目玉機能は線形型LinearTypesとQualifiedDo拡張だろう。筆者のような浮動小数点数オタクにとっては多倍長整数の実装がghc-bignumに切り替わったのがでかい。Apple Silicon対応は9.0.1には間に合わなかった。
ghc-bignumは従来のinteger-gmpとinteger-simpleを統合したような代物である。デフォルトではGMPバックエンドが使用されるが、GMPバックエンドが使用できない場合の実装はinteger-simpleよりも速い。
新しい実装にはバグがつきものである。alpha1の段階で筆者が遭遇したバグの顛末は
- GHCデバッグ日誌(2020年10月)
に書いた。残念ながらリリースまでに発見されなかったバグもある:
ので、GHC 9.0.1をプロダクションで利用するのは避けたほうが良いだろう。ただ、使わないことにはバグは発見されないので、GHC 9.0.1を評価目的で使用するのは積極的にやっていくべきだ。
GHC 9.2系
GHC 9.2系も準備が進んでいる。ghc-9.2ブランチが既に作成された(当面はmasterに追従するっぽい)。
- Release tracking ticket for 9.2.1 (#19321) · Issues · Glasgow Haskell Compiler / GHC · GitLab
- 9.2.1 · Milestones · Glasgow Haskell Compiler / GHC · GitLab
筆者の独断と偏見で選んだ新機能と非互換な変更点を挙げてみる:
- RecordDotSyntax拡張
- NoFieldSelectors拡張
- 実装の当事者であるfumieval氏による記事: 新しいGHC拡張、NoFieldSelectorsについて – モナドとわたしとコモナド
- GHC2021拡張
- https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0380-ghc2021.rst
- GHC 9.2ではGHC2021がデフォルトになるようだ。マジ!?
- GHC2021を使いたくない場合は、Haskell2010拡張を明示的に有効にする。
- Char カインド
- Apple Silicon対応の関係で、
IntN
/WordN
(N=8,16,…)の内部表現がInt#
/Word#
からIntN#
/WordN#
に変わる。また、Int64#
,Word64#
が常に定義されるようになる。 - AArch64 NCG
- Data.Semigroup.Optionが削除される。
- Deprecate and Remove Data.Semigroup.Option and Data.Monoid.First/Last (#15028) · Issues · Glasgow Haskell Compiler / GHC · GitLab
- Option型については Haskellerのためのモノイド完全ガイド でちらっと紹介した。GHC 8.4以降では不要となっていた代物だ。
- GHC.TypeLitsの
(<=?) :: Nat -> Nat -> Bool
の定義が変わる。 - GHC.TypeLits/TypeNatsの Nat カインドが Natural 型のエイリアスに変わる。
- BoxedRep
- Levity polymorphism関連で、LiftedRepとUnliftedRepがBoxedRepに統合される。
- RISC-V 64バックエンド(LLVMのみ)
おわり
筆者がHaskellを使い始めて10年以上になる。その間、ただ使うだけではなく、(日本語)記事を書いたりパッケージを作ったりして、Haskellコミュニティーに貢献するように心掛けてきた。そして今回ついに「GHCに貢献する(自分の書いたコードを取り込んでもらう)」ことができ、達成感を感じている。
今回、筆者が投げていたMR 2つがマージされて一段落したわけだが、GHCに対するパッチのネタは他にもいくつか持っているので、そのうちまたMRしたい。
ところで、去年掲げていた「GHCに貢献する」という目標が去年のうちに叶わなかったのは、当時筆者が持っていたマシンが2013年モデルとかで、性能が劣る(のでビルドに時間がかかる→ビルドが億劫になる)という要因が大きかったように思う。この点に関しては、去年の年末に新しいMacを2台(Intel MacとApple M1 Macをそれぞれ1台ずつ)手に入れたので、GHCのビルドはの心理的な障壁が下がった(具体的には、quickビルドが1時間未満で済むようになった)。強いマシンは正義である。
MR マージおめでとうございます!素晴らしい仕事です。
特に浮動小数点に関わる箇所なので胸アツですね。
精力的に活動されているようで刺激を受けました。
ピンバック: GHCいじくり日誌・AArch64編 | 雑記帳