mrubyをWebAssemblyで動かす(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社エンジニアの「?」を「!」に。原著者の許諾を得て翻訳・公開いたします。Black日本語タイトルは内容に即したものにしました。gemをお試しください。2018年の世界へようこそ。興奮を呼び起こすイノベーションがコンパイラ界隈にも巻き起こりつつあります。ARやVRや機械学習など先端を行く話題はいくつもありますが、ソースコードを機械が実際に理解できる形に変換する役割を担うコンパイラの歴史に比べればまだまだ新顔です。コンパイラの復権は主にLLVMのおかげです(LLVMはモジュール形式のコンパイラインフラストラクチャプロジェクトです)。LLVMの重要性については、Wiredの2013年の記事に的確な指摘がまとまっています。その希望とは、LLVMがソフトウェア開発の新時代の水先案内人となり、アプリケーションがあるマシンから別のマシンに、そしてあるプロセッサから別のプロセッサにすら自由に移動できるようになることだ。以来5年が経過し、これはかなりの部分まで実現しました。LLVMの導入とそれがもたらすモジュラーパラダイムによって、ソフトウェア開発が根底から変革されました。LLVMによって、移植性/最適化/言語実装/コンパイルライフサイクル全体がラディカルに考え直されたのです。LLVMをご覧ください。Swiftの創立者でもあるのです。Podcastで聞くことができます。LLVMがWebといったい何の関係があるのでしょうか?Webのプログラミングといえば、その草創期(1995年)からJavaScriptと相場が決まっていました。JavaScriptはその後23年間批判と論争の嵐をくぐり抜け、今もWebプログラミング言語として居座っています。JavaScriptを置き換える強力なライバルが不在であったにもかかわらず、言語としてよい感じに成熟と進化を繰り返してWebに関わり続けています。Webブラウザも、静的コンテンツを表示するだけの単なるビューアから、数十億ものユーザーやシステムの基盤を追い求める企業の要求に後押しされたフル装備のアプリケーションプラットフォームへと着実に進化しています。ブラウザが「正しい」アプリケーションプラットフォームになるにはまだひとつ足りないものがあります。Wired誌の同じ記事から再度引用します。JavaScriptの難点は、言語としては比較的シンプルであることだ。他の言語(Javaや、誉れ高きCやC++など)ならできることでもJavaScriptでできるとは限らない。私たちに必要なのは、アプリケーションをどんな言語で書いてもあらゆるマシンで動かせる方法だ。それも大きな妥協を強いられずに。LLVMはそれを目指している。少なくともそう思っている人々がいる。ブラウザがデスクトップOSやモバイルOSに匹敵するには、ブラウザに欠かせない例のあの言語からブラウザ自身が解放されなければなりません。ブラウザのプログラミング環境は、他のプラットフォームと同様もっと低レベルになるべきです。ブラウザは独自のアセンブリ言語を備え、特定のハードウェアに依存しないよう抽象化しつつ、マシンそのものにできるだけ近いところに配置されるべきです。2012年に始まったasm.jsプロジェクトも同じものを目指していました。asm.jsは、JavaScriptのサブセットを定義することで、コンパイラにとって理想的なターゲット言語になるべくJavaScriptのコンセプトを絞り込みます。Zakaiが生み出したLLVMからJavaScriptへのコンパイラであるEmscriptenというツールがすべてを可能にしました。今や私たちは、CやC++で書かれたプログラムを次のようにJavaScriptにコンパイルできます。2014でのEmscriptenやasm.jsについてのスピーチ↓をご覧ください。asm.jsの登場によってWebのアセンブリ言語が産声を上げたのです。これはあらゆるブラウザで標準化および統合化される可能性を秘め、そして実際に2015年のWebAssemblyのクロスブラウザ化作業の開始でまさにそれが起きたのです。WebAssembly”も参照)。2年の間にコミュニティは仕様を定めてプロトタイプをいくつも作り、Webプラットフォームに責任を持つ最大のあの企業と激論を戦わせ、主要な全ブラウザでWebAssemblyをリリースしたのです。これは誇張抜きで歴史に残る偉業です。ブラウザは最初からこうあるべきだったのでしょう。ブラウザは低レベルのコンパイルターゲットを提供して、多くのプログラミン言語もWebでプログラミングできるようにし、既存のネイティブツールやライブラリやアプリも使えるようにすべきだったのです。ブラウザが今日のような地位を占めるようになると一体誰が予測できたでしょうか。いうものの、今や私たちがハッピーになれる道が開けてきましたし、そこに秘められた能力にはいやが上にも期待します。MozillaがWebAssemblyはゲームを変革すると考えています。Studioをご覧ください。RubyをWebに持ち込む多くの人がWebで他のさまざまな言語を何年も使っていますが、そのためにはJavaScriptを使わないわけにいきません。その最もよく知られた例はCoffeeScriptでしょう。CoffeeScriptは2009年に始まり、JavaScriptにうんざりした人(またはJavaScriptに戻ってきた人)に別の選択肢をもたらしました。RubyやPythonに似た拡張文法で書くことができ、純粋なJavaScriptにトランスパイルされます。素晴らしいじゃないですか!それから何年もの間source-to-sourceコンパイラが全盛でした。JavaScriptにコンパイルされる言語の長大なリストを見ると何とも言えない気持ちになります。Rubyも例外ではありません。その中でも優れものはOpalです。このsource-to-sourceアプローチには、効率やパフォーマンス、低レベル最適化の喪失などいくつも短所があります。JavaScriptを歪めてまで需要を満たそうとする必要がそもそもあるのかという根本的な批判もあります。WebAssembly登場以前であれば、その答えは単純そのもので、他に手段がないのだからそうするしかなかったのです。それが今や他の手段があるのです。この新しいコンパイルターゲットを利用して、実にさまざまな言語をWebプラットフォームに暖かく迎えようという動きが始まっています。他の言語たちは正確にはどんな形でWebにやってくるのでしょうか?CやC++などのネイティブ言語は既にLLVMへのコンパイルが可能ですし、LLVMのその他の実装(Rustなど)も、コンパイラツールチェインのターゲットをWebAssemblyに設定するという素直な手順でできます。Rubyのように、インタプリタやVMを自前で持つ動的言語だと少々難しくなります。その理由を知るために、Rubyの主要な実装であるMRI(CRubyとも呼ばれます)を見ていくことにしましょう。どんなプログラミング言語であっても、構文の仕様よりはるかに多くのものが含まれています。したものは、言語の実装や利用状況によっても定義されます。Rubyの場合は伝統的に、Unix的な環境に依存する各種ツールの総合的なエコシステムを意味し、ファイルシステム、コンパイラツールチェイン、標準ライブラリ、ネットワーク、その他OSが提供することが期待されるあらゆるものが含まれます。MRIすなわちCRubyはそれらを期待していますし、そのことは別に問題ありません。Rubyの使われ方、たとえばRailsアプリを実行するWebサーバーなどは、このモデルに完全に適応していて、その利点をすべて享受できます。利用状況が変わるとそうはいかなくなります。Rubyアプリをエンドユーザーに配布するなどです。期待するRubyインタプリタをローカルにインストールし、どのアプリにも正しいバージョンのgemを提供するとき、一般人と開発者と正規ユーザーを同じに扱うわけにはいきません。Rubyインタプリタを丸ごとアプリに仕込んでリリースするという回避方法もなくはありませんが、どう考えても現実的ではないでしょう。鼻で笑わないでください。一応実際に可能なのですから。Rubyは、Windows/Linux/macOS向けにRubyを同梱したアプリパッケージを作成するという印象的なプロジェクトです。2017の秀逸なトークでは、Rubyアプリを単一の実行ファイルに固め、メモリ上に仮想のファイルシステムを作成するなどの裏技を駆使して無理やり動かすというデモを披露しました(動画とコード)。Piというライブコーディング向けミュージックシンセサイザーです。「インタプリタ同梱」的なアプローチは使えるは使えますが、サイズが相当巨大になるなど、いろいろ制約があります。MB、1,396ファイルにのぼります。PIのようなデスクトップアプリなら大したことはありませんが、Webではそうはいきません。MBをプッシュすることもあるようですが(某NYTimes.comとか)、これはあんまりです。それにまだ私たちはアプリのコード量を計算に入れていません。ファイルサイズだけではなく、MRI/CRubyの起動もかなり重く、メモリのフットプリントも相当な量になります。MRI/CRubyインタプリタをブラウザに丸ごと同梱することもできなくはないかもしれませんが、現実的とは思えませんし、そもそも必要ですらないでしょう。Rubyの実装だけの問題ではないことは明らかです。効率的で軽量なRubyがどこかにあれば、インタプリタの必要な部分だけを取り出して、管理可能な小さいバイナリにコンパイルできるのではないでしょうか。おお、そういえばそんなものがあるじゃないですか!mrubyのあらましmrubyのことをご存知ない方は、この機会にぜひお見知りおきを。個人的には、Rubyコミュニティで今アツいのはmrubyだと思います。mrubyプロジェクトを率いるのはRubyの作者としてお馴染みのMatzその人です。mrubyプロジェクトは2012年に組み込み(eMbedded)のユースケースに適した言語の実装としてスタートしました。組み込みの分野ではメモリやストレージといったリソースが限られています。ウォッシュレットのプログラミングも組み込みのひとつです。mrubyは完全にモジュール化されているので、MRI/CRubyのような巨大なインタプリタのモノリスをインストールするのではなく、好きなだけカスタマイズできます。ファイルシステムやネットワークアクセスが不要なら取っ払ってしまえばよいのです。ユーザーにできることを制限したいならそれもできます。mrubyはあなた好みのインタプリタをビルドできるのです。「完全なRubyとは違う」という制約はあるものの、利用状況に応じてmruby言語の主要な実装から本質的なメリットを得られます。Bovensiepenへのインタビューがおすすめです。mrubyは、インタプリタのアーキテクチャを考え直すことで新たな可能性の扉をも開きました。そのひとつが今私たちが興味津々の「Rubyスクリプトをネイティブコードにコンパイルする機能」です。プログラムを配布するのにありがたい機能ですね。アプリを単一の小さなバイナリで作成できるということは、Rubyの移植性が著しく高まるということです。マシンを問わずらゆる主要なプラットフォームで動く単独の実行ファイルを簡単に生成するmruby-cliのようなプロジェクトもあります。mrubyのバイトコードはCのプログラムに直接埋め込むこともできますので、clang(C言語ファミリーのLLVMフロントエンド)でコンパイルできます。コンパイルは次のような流れになります。ネイティブ実行ファイルここまでご理解いただけましたでしょうか?mrubyからWebAssemblyまでコンパイラのターゲットをLLVMにできるなら、WebAssemblyへの道が見えてきます。先ほどのEmscriptenがLLVM-to-JavaScriptコンパイラだったことを思い出してください。EmscriptenはWebAssemblyにコンパイルする機能を獲得しつつあります。Rubyは次のような旅路を辿ることになります。WebAssemblyちょっと無理くりすぎな感もありますが、役者は揃いつつあります!フローの中にひとつ見慣れないものがありますが、BinaryenはWebAssembly向けのコンパイラ/ツールチェインインフラストラクチャです(名前はbinaryとEmscriptenのもじり)。AlonはBinaryenのコンパイルでWebAssemblyを生成するスピーチも行っています。実際に手を動かしてやってみましょう。Started”を参照)。SDKをダウンロード/インストールして有効にします。cloneで取り、他の開発ツールと同じところに置くことをおすすめします。ツールの一部はまだGitHubの個人アカウントのリポジトリに置かれています。orgのようなもっとオフィシャルな場所に統合されるのかなと思ったりもします。./emsdk_env.shのように実行)。SDKでは独自のclangとnodeが提供されるので、通常の開発で使っているバージョンと衝突する可能性があります(Rubyをソースからコンパイル/インストールしてgemのネイティブ拡張をビルドするときにそのバージョンで失敗するなど)。そこで私は、常に新しいシェルで実行するのではなく、自分の.bash_profileへのエイリアスを追加して、WebAssemblyを使うシェルでのみwasm_initでSDKをアクティベートするようにしました。~/Developer/emsdk/emsdk_env.sh"以上でEmscripten/WebAssemblyツールチェインが使えるようになります。次はmrubyをインストールする必要があります。mruby)でインストールすることをおすすめします。libmruby-devを使います。WebAssembly向けのmrubyインタプリタをビルドします。GitHubのmrubyリポジトリをcloneしましょう。https://github.com/mruby/mruby.gitmruby/ディレクトリにcdします。ここからいよいよ面白くなってきます。mrubyインタプリタの実際のビルド設定は、シンプルなRubyスクリプトそのものであるbuild_config.rbファイルを定義してrakeを実行することで行います。リポジトリにあるデフォルトの設定には便利なboilerplateがいくつも詰まっていますし、ドキュメントもかなり詳しく書かれています。今回のニーズに応じて、設定ファイルの内容を以下で完全に置き換えることにします。'emar'end新しい設定ファイルは、gccコンパイラツールチェイン(自分のシステムで同等に近いものなら何でも構いません)を用いてホストシステム向けのネイティブコンパイルを作成するようmrubyのビルドシステムに指示します。--versionで確認できます。x86ホストシステム向けのバイナリをビルドします。WebAssemblyをターゲットとしてクロスコンパイルしたいので、ここで上のCrossBuildブロックにご登場願うことになります。Archiver)を使うよう指定します。-Osコンパイラフラグは、コードサイズ削減などほとんどの最適化を有効にするオプションです。クロスコンパイルはかなり強力で、私が別プロジェクトで作っているmrubyのiOS/tvOSフレームワークでは、静的ライブラリやヘッダーがすべて単一パッケージに同梱されています。rakeでmrubyのWebAssembly向け静的ライブラリをコンパイルする準備が整いました。build/ディレクトリが作成され、その下にhost/ディレクトリやemscripten/ディレクトリも作成されます。欲しい静的ライブラリはemscripten/lib/libmruby.aにあります。..でmruby/ディレクトリを抜けます。今度はWebAssemblyをコンパイルする以下のシンプルなスクリプトを書いてhello_ruby.rbに保存します。Ruby!"コンパイラを起動して、このスクリプトからmrubyのバイトコードを生成します。hello_ruby.rbここでちょっと解説します。mrbcはmrubyコンパイラであり、-Bはバイトコードを生成してhello_rubyという名前のCの配列に置くよう指示します。最後のhello_ruby.rbは、コンパイルしたいファイルです。生成された新しいCファイルはhello_ruby.cという名前で、バイトコードを表す16進整数の配列を含んでいます。{0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x34,0xcd,0x4c,0x00,0x00,0x00,0x65,0x4d,0x41,0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x47,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x3f,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x04,0x06,0x00,0x80,0x00,0x3d,0x00,0x00,0x01,0xa0,0x00,0x80,0x00,0x4a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x0b,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x52,0x75,0x62,0x79,0x21,0x00,0x00,0x00,0x01,0x00,0x04,0x70,0x75,0x74,0x73,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,};このままでは単なる静的データでしかありません。これを読み込んで実行するにはもうひと頑張りが必要です。バイトコードを含むhello_ruby.cファイルに以下のコードを追加しましょう。0;}このスニペットではいくつかの作業を行っています。コアmrubyと中間表現(バイトコード)ヘッダーをインクルードしています。main()関数ではmrb_open()でmrubyコンテキストを開き、mrb_load_irepでバイトコードを読み込んで実行し、最後にコンテキストを閉じます。シンプルですね!いよいよhello_ruby.cファイルをEmscriptenでWebAssemblyにコンパイルする準備が整いました。次のようにemccコマンドを実行します。Frontend(LLVMのフロントエンド)。WebAssemblyをオンにする。スペース削減などの最適化を行う(オプション)。includeディレクトリを探索する。JavaScriptファイルの生成を指示するこの拡張子が重要です(後述)。Compilerを実行して、Emscriptenの生成したJavaScriptを最小化する。必須ではないがこれもスペース削減に有効な最適化。hello_ruby.wasmとhello_ruby.jsという2つのファイルが生成されます。KBにしかなりません。「ここでJavaScriptを生成している理由って何?」と疑問に思うかもしれません。私もこれを最終的に置き換えるんだと思っていたのですが、それは時期尚早です。このちっぽけなJavaScriptが実はなかなか役に立つことがわかります。.wasmファイルを読み込むときに<script>タグを単純に使うことはできません。type="module">でWebAssemblyモジュールをES6モジュールのように読み込めるようにする計画があります)。生成されたJavaScriptは.wasmバイトコードをWebAssembly.Moduleにコンパイルしてインスタンス化します。関数のimportやexportも設定され、メモリが設定されてアロケーションされ、コンソール出力に使うTTYなどの他の共通サービスはJavaScriptでエミュレーションされます。1を付けずに実行し、生成された誉れ高きJavaScriptコードを調べてみましょう。API”という記事が多少なりとも参考になるでしょう。Alonは単独のWebAssemblyをビルドするガイドも書いています。これで先ほどのささやかなJavaScriptが必要な理由が理解できましたので、これをページに読み込むシンプルなHTMLテンプレートを書いてみましょう。このテンプレートで、私たちの苦心の成果であるhello_ruby.wasmバイナリファイルをフェッチして実行します。src="hello_ruby.js"></script></html>このファイルをhello_ruby.htmlという名前で保存します。このHTMLファイルをダブルクリックしただけでは期待どおりに動きません。APIリクエストがブロックされるからです。代わりにこのHTMLファイルをWebサーバーで公開しなければなりません。WebサーバーはRubyで簡単に実行できます。-p8000これでシンプルなWebサーバーが起動し、8000番ポートでカレントディレクトリの静的ファイルを公開します。Ruby!」が表示されます?。themeテーマで使っています?)そこそこ手のかかる作業なので、みなさんがもう少し簡単にこの実験を行える方法を編み出しました。gem本記事のビルドプロセス全体を1個のgemに収めました。以下を実行してインストールします。上述の環境変数も設定する必要があります)。これで、ruby-wasmユーティリティでRubyスクリプトをWebAssemblyバイナリにコンパイルして上述のJavaScriptファイルやHTMLファイルを簡単に生成できるようになります。serveでHTMLページを公開すればおしまいです。詳しくはGitHubのblacktm/ruby-wasmリポジトリのgemをご覧ください。皆さんも私と同様、今後の行方が気になっていることでしょう。WebでRubyが走ることの意義とは一体何なのか、実際にはどんなことができるようになるのか、そうした可能性の追求は自己満足にとどまってしまうのか。その辺は私にも読みきれませんが、ひとつ言えるのは、現在の可能性の多くがWebAssemblyを強化するものであるということです。WebAssemblyの強みを雑に3つのカテゴリに分けると、パフォーマンスと効率性、ネイティブ世界とのやりとり、そして移植性になるかと思います。パフォーマンスや効率に関しては、正しいコンパイル手順が使えるようになったことで分析や最適化が可能になっています。LLVMを用いることで、さまざまな最適化機能が既に使えるようになっていますし、今後の最適化機能の登場も期待できます。WebAssemblyは既に高速かつ高効率ですが、WebAssemblyのVMやランタイムコンポーネントも長年改良を重ねてきています。これは、サイズの小さい高速かつ高効率なRubyインタプリタがブラウザで使えるということであり、私たちが選ぶアプリがWebで使えるようになるというメリットを得られるということです。ネイティブ世界とのやりとりについても素晴らしい可能性が秘められています。ネイティブコードにコンパイルするために書かれたソフトウェアにインターフェイスを持たせられるということです。それらの言語で書かれた既存のライブラリやツールは膨大な数にのぼり、WebAssemblyを用いればそれらをWebに持ち込めるようになります。Layer(SDL)です。SDLは、グラフィックス/音声/コントローラ入力への低レベルアクセスを提供する、クロスプラットフォームのゲーム開発ライブラリです。SDLはブラウザAPIをサポートしただけでなく、今では従来のネイティブプラットフォーム向けに書かれたゲームを移植することが可能になりました。Gardenなどのクールなデモがいくつか公開されています。私たちRubyistにとっての可能性は、最もよい結果を得られる低レベルコンポーネントを引き続き使えることと、それらをRubyでより高度かつより概念的なレベルでオーケストレーションできるということです。2Dというプロジェクトがまさにそのよい例です。2Dの上に構築したものですが、今やSDL上で構築されています。2DをWebAssemblyで動かすことです。2017の私のトークをご覧ください。最後は移植性、つまりRubyアプリやRubyインタプリタをどこにでも持ち込める機能についてです。mrubyは移植を完全に可能にするためのマジックに満ち満ちています。mrubyは、clangやLLVM、そしてあらゆるCコンパイラツールチェインをコンパイルターゲットにできるので、Rubyアプリはあらゆるプラットフォーム向けにコンパイル可能です。Playgroundが早く使えるようになる日を心待ちにしていますし、Arduboyも面白そうです)に注力してきましたが、将来はずっと広がっていくでしょう。Rubyアプリの.wasmバイナリは信じられないほど移植性が高く、サーバーサイドでもクライアントサイドでも動かせます。Lambdaは未だにRubyをサポートしていません(残念!)が、代わりにRubyのWebAssemblyバイナリをデプロイすればよいのです。RubyのWebAssemblyバイナリは移植性が高いだけでなく、MRI/CRubyで動くRubyスクリプトより安全かつ高速です。行く手には興味の尽きない膨大な可能性が広がっていることは間違いありません。Ruby(や他の言語)がJavaScriptに取って代わることはないだろうと睨んでいます。Webブラウザには、非常に高い能力と何十年にもわたって最適化されてきたVMを持つプログラミング言語があるのです。ES6がもたらす現代的な特典をもれなく利用できますので、JavaScriptで書くことは今や悪いことでも何でもありません。RubyをあえてWebに参入させたいのであれば、Opalという実に手堅いオプションを用いれば、RubyのJavaScriptへの統合とやりとりを信じられないほど簡単に行えます。最後に、「ネイティブアプリ」と「Webアプリ」の定義の境界線は今後もあいまいになるだろうとだけ申し上げておきます。Apps(PWA)などのアプローチは、ネイティブ/Webを問わず最良の結果を開発者にもたらし、エンドユーザーが楽しむWebエクスペリエンスをさらに向上させ、Webの技術をデスクトップアプリとモバイルアプリのどちらにももたらします。「本当にそんなことが可能なのか?」という疑問はじきに無意味になるだろうと想像しています(ほぼほぼ「イエス」なのですから)。「それは本当によいものなのか、そしてそれをどう確かめたらよいのか?」という懸念点を解消する方が、ずっと困難で時間もかかるでしょう。最終的にどうなるのかは誰にもわかりませんが、この旅路は私たちを惹きつけて止まないことは確かです。gemでどしどし遊ぶようになることを願っています。ご意見/ご感想がございましたら、本記事のツイートに返信いただけます。このgemで何か面白いアイデアを思いついた方や、そうしたアイデアを知りたい方は、GitHubのissueを自由にオープンいただけます。いつものようにメールでお問い合わせいただくこともできます。コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。enno.jpを運営。実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。Storageを使ってはいけない(翻訳)#Tags#会社紹介#自社サービス#勉強会#Rails#PHP#デザイン#実績#週刊RailsウオッチCONTACTお気軽にご相談ください。(翻訳)CONTACTTechRachoでは、パートナーシップをご検討いただける方からのご連絡をお待ちしております。お気軽にご意見・ご相談ください。