オフチェーンコードの翻訳にGHCJSクロスコンパイラーを使用するスマートコントラクト
2020年6月4日 Luite Stegeman読了時間8分
Haskellチームによるテクニカルな記事をお届けする「開発者が掘り下げる」シリーズ第2弾。このシリーズでは、Cardanoプラットフォームとプロトコルのコアとなる要素をありのままの姿で紹介し、エンジニアリング上の選択に至る背景をお届けしています。今回は、CardanoのスマートコントラクトプラットフォームであるPlutus(プル―タス)の、ライブラリーおよび開発者ツールを改良する取り組みについての概要を紹介します。
はじめに
IOHKでは、Cardanoブロックチェーン用のスマートコントラクトプラットフォームPlutusを開発している。PlutusコントラクトはHaskellプログラムであり、一部がオンチェーンのPlutus Coreコードに、一部がオフチェーンコードにコンパイルされている。オンチェーンコードはCardanoネットワーク上で、Cardano台帳に埋め込まれたスマートコントラクト言語であるPlutus Core用インタープリターを使用して実行されている。これがネットワークがトランザクションを検証する仕組みである。オフチェーンコードは、コントラクトのセットアップといったタスクやユーザーインタラクションのためにある。これはnode.jsランタイム上で、コントラクトの各ユーザーのウォレット内で稼働する。
Plutusのオフチェーン部分をコンパイルすることは、したがってオフチェーンコードのJavaScriptへの翻訳が絡んでくる。これをなすために、GHCJS(Glasgow Haskell to JavaScriptクロスコンパイラー)が使用されている。
ここ一年ほど、GHCJSコードジェネレーターにはあまり変更が加えられていない。その代わり、GHCJSを使用したコンパイルの信頼性と予測可能性を高めるために一部を再構成するとともに、Windowsのサポートを追加し、最新のCabal機能を活用できるようにした。本稿では、これまでの足跡と今年の予定について概観する。
Cabalサポート
GHCJSでパッケージをインストールする際、おそらく--ghcjs
コマンドラインフラッグを使用するか、コンフィグファイルにcompiler: ghcjs
を入れるだろう。これにより、Cabalでghcjs コンパイラーフレーバー
が有効化される。ghcjs
のフレーバーはghc
フレーバーに基づいており、GHCJSが構築したセットアップスクリプトの実行やJavaScriptソースの追加をサポートするなどといった機能を追加する。
Cabalにより、近年多くの機能が導入された。これにはバックパックのサポート、Nixスタイルのローカルビルド、パッケージごとの複数の(名前付き)ライブラリー、コンポーネント単位のビルドプランが含まれる。残念ながら、新機能によりコードベースに多くの変更が必要となったことで、ghcjs
フレーバーのメンテナンスにはかなりの遅れが生じ、GHCJSのサポートが最新に更新されたのはバージョン3.0。したがって、新スタイルのビルド機能を使用する場合は、必ずバージョン3以降のcabal-installが必要となる。
ghcjs
とghc
コンパイラーフレーバーの違いは小さなものだ。そしてCabalのクロスコンパイルサポートは向上している。したがって、最終的にはghcjsコンパイラーフレーバーを完全に放棄できると期待している。代わりに、プラットフォーム固有のビヘイビアとしてghc
フレーバーの拡張子を追加できる。
コンパイラープラグイン
GHCにより、プラグインによるコンパイラーの拡張が可能となる。これで、コンパイルパイプラインのアスペクトを変更できる。例えば、プラグインにより新たな最適化機能を導入したり、タイプチェッカーを拡張したりできる。
Quasi型クラスの抽象化を通じてコンパイラーから分離されたTemplate Haskellと異なり、プラグインはGHC API全体を直接使用できる。これにより、Template Haskellをクロスコンパイラーで実行するためにGHCJSが導入した「外部インタープリター」アプローチがコンパイラープラグインには不適切となり、代わりに、プラグインをビルドプラットフォーム(GHCを実行)用に構築する必要がでてくる。
2016年、GHCJSはコンパイラープラグインの実験的サポートを導入した。これはGHCJSパッケージデータベース内におけるプラグインのルックアップに依存し、GHCパッケージデータベースからプラグインパッケージとモジュールに近いものを見つけようとする。私たちはGHCJSに構築されたシステムで実行可能なパッケージを示す新しいフラッグを追加した。これによりプラグインは、新スタイルのビルドや他の「エキゾチック」なパッケージデータベースコンフィグで再び使用可能となる。
原則として、新フラグによりプラグインはあらゆるGHCクロスコンパイラー上で使用可能であるが、プラグインをクロスコンパイラーでも構築するための要件はかなり醜悪だ。現在この要件を取り除くための作業を進めており、その後クロスコンパイラー用プラグインサポートをアップストリームGHCに融合させる( チケット14335、チケット17957参照)。
Emscriptenツールチェーン
大昔、GHCJSはWindowsで作動した。ごく少数の勇者が実際に使ってみたかもしれない。そのブートパッケージ(ghcjs-boot
が構築したパッケージ)は、WindowsビルドプラットフォームのWin32パッケージを含んでいた。この理由はGHCJSによるCabalコンフィグである。ビルドプラットフォームがWindowsである場合、Cabalのos(win32)
フラグが設定された。当時は、単にパッケージをGHCJSでエラーなしに構築して、それをブートパッケージに含むのが最も簡単だった。しかし、Win32
パッケージは実際には作動せず、常に最新の状態に保つことはメンテナンスにおける負担となっていた。ある時点で更新し損ね、以降GHCJSはWindows上で作動しなくなった。
WindowsでブートパッケージがWin32
を必要としたことは、ビルドプラットフォーム(コンパイラーを実行)とホストプラットフォーム(コンパイラーにより生成された実行ファイルを実行)間でうまく分離がなされていなかったことを示している。これは、GHCJS用が適切なCツールチェーンを欠いていたことに起因する。多くのパッケージが単にHaskellコードを持つだけでなく、例えばAutotools configure
スクリプトやhsc2hs
経由でCツールチェーンが生成したファイルを持つ。
GHCJSアプローチとは一部の前もって生成されたファイルを含むことであり、その他すべてにビルドプラットフォームのCツールチェーン(GHCが使用するのと同じ)を、壊れないことを期待しながら使用することであった。壊れたら、パッケージをパッチすることになっていた。
近年、コンパイル対象としてのウェブブラウザーは着実にトラクションを増やしている。Emscriptenは長年にわたりCツールチェーンを提供しており、近年自身のコンパイラーバックエンドから標準LLVMバックエンドを持つClangコンパイラーへと切り替えた。
ClangはGHCがCツールチェーンとしてサポートしている。これはasm.jsと、ブラウザーで直接実行できるWebAssemblyコードを出力可能だ。残念ながら、このコンパイラーのユーザーは、コンパイルしたCコードとGHCJS内のC FFI(foreign import ccall
)を通じて直接やり取りすることはまだできない。しかし、Cツールチェーンを持つことにより、configure
スクリプトやhsc2hs
に依存するパッケージのコンパイルは信頼性が高まる。これにより、一部の長期にわたるビルド問題が解消され、Windowsのサポートが再び可能となる。私たちは、これは既に追加依存に値すると考えた。
Emscriptenツールチェーンを使用するGHCJS 8.6のバリアントは、Windowsにインストール可能なghc-8.6-emscripten
ブランチで有効である。今回は、ブートパッケージのセットはすべてのビルドプラットフォームで同一である。EmscriptenはGHCJS 8.8以降標準ツールチェーンとなる予定である。
コード組織
初期のある時点でGHCJSはGHCのパッチとして実装された。これは、通常のGHCインストールにより生成されるSTG中間コードからJavaScriptを生成するもので、これにより、GHCJSによるパッケージのインストールが容易になった。普通にインストールするだけでJavaScriptが付属し、Template Haskellすらも作動した。
このアプローチの欠点は、ビルドプラットフォームが生成されるコードに多大な影響を与えることだ。64ビットLinuxマシンで構築した場合、すべてのプラットフォーム定数はLinuxプラットフォームから生じ、コードはInt
が64ビットであるという前提で構築される。これはJavaScriptに実装する際にあまり効率的でない。さらに、CabalはJavaScriptが生成されたことにまったく気付かない。
その後、ghcをライブラリーとして使用するように切り替え、必要に応じてコンパイルパイプラインを変更するためにHooks
を導入した。これにより、GHCJSプラットフォームのワードサイズをビルドプラットフォームから切り離し、JavaScriptFFI
拡張子を導入し、Template Haskellをnode.jsで分離したプロセスで実行することが可能となった。
残念ながら、アップストリームghc
ライブラリーにおける変更についていくことは難しいことが判明した。加えて、既存のghc
をHooks
経由で修正することにより、エンジニアたちはアップストリームの問題を直接修正する代わりに回避するようになった。
2018年初頭、私たちはGHCJS用にghcカスタムライブラリーを構築することを決め、ghc-api-ghcjs
としてインストールした。これで深刻な不具合はアップストリームに融合される前に回避できるようになった。最近この独立したライブラリーは廃棄され、GHCとGHCJSソースコードはともに同じライブラリーghcjs
に構築されている。
GHCJSをGHCビルドシステムで構築することはまだ不可能であるものの、現在再びアップストリームGHCソースツリーをかなり直接的に使用している。それは過去への回帰といえるかもしれない。しかし今回はツールチェーンとビルドツールを備えたプラットフォームを使用しており、前回このアプローチに多くの問題を生じさせていた落とし穴を避けることができる。
展望
2019年、Cabalを再び使用可能とし、Windowsのサポートを再開、そしてCツールチェーンを向上させるための改良がおこなわれた。私たちは、GHCJSコードベースにおける基礎的な変更がアップストリームの融合を簡易化し、わずかな変更によりライブラリーに互換性を持たせることができるようになると期待している。GHCJSを、適切なツールチェーンを備える一方でカスタムツールを減少させて、適切なクロスコンパイラーに調整しながら、GHCJSとGHCを近づけ、最終的に融合させることを目指している。単純にクロスコンパイルが不可能なプラットフォーム限定ライブラリーが常に存在するなかで、私たちの究極的な目標はJavaScriptターゲットで、x86_64、ARM、AArch64やその他のコード生成ターゲットに即してGHCを提供することだ。