CLOSE

Ruby meets WebAssembly (全2記事)

Rubyを手軽にブラウザ上で動かすことが可能に WebAssemblyを使って解消する、Rubyの動作環境問題

プログラミング言語Rubyの国内最大級のカンファレンス「RubyKaigi」。「RubyKaigi 2022」のKeynoteで登壇したのは、齋藤優太氏。「Ruby meets WebAssembly」のテーマで、WebAssembly上でRubyが動くようになった経緯、テクニック、ユースケースについて発表しました。全2回。前半は、なぜWebAssembly対応をするのか、そのモチベーションと経緯について。

登壇者の自己紹介とアジェンダ紹介

齋藤優太氏(以下、齋藤):いやぁ~、緊張しますね(笑)。みなさんおはようございます。

会場:おはようございます。

齋藤:「Ruby meets WebAssembly」ということで、今日は最近少し話題になっているRubyとWebAssemblyの話をしようと思います。よろしくお願いします。

(会場拍手)

RubyがWebAssembly上で動くようになったので、今日の話はその紹介になります。何事もなければRuby3.2の機能として出荷される予定です。今日は始めに、なぜWebAssembly対応をするのかというモチベーションを紹介して、RubyのWasm対応デモを交えながらWebAssemblyのユースケースについて紹介します。後半は、Wasm対応にあたってどのようなことをしたのかという具体的な技術的な内容になると思います。

あらためまして、Rubyのコミュニティでは初めましての方が多いと思うので、少しだけ自己紹介をさせてください。齋藤といいます。インターネットでは「kateinoigakukun」というハンドルネームで活動しています。本業は早稲田大学の学生なのですが、GoodNotesというノートアプリの会社で、ソフトウェアエンジニアとしても働いています。

Rubyとの関わり方としては、2021年のKeynoteで、遠藤さん(Yusuke Endoh氏)が紹介されていたTypeProf for IDEのお手伝いをクックパッドのインターンとしたり、秋頃からは、CRubyのWebAssembly対応の作業をやっています。その流れで2022年の頭からは、CRubyのコミッターとしてWasmポートのメンテナーをしています。

SwiftでもコミッターとしてWebAssembly対応をやっていたり、Link-time-optimizationの実装や、コンパイラからインタプリタまでいろいろとやっています。

なぜRubyがWebAssembly対応をするのか?

さっそくですが、内容に入っていきましょう。始めに、モチベーションですね。RubyとSwiftって何から何まで違う言語ですよね。そんな2つの言語でコミッターをしている変な人なんですが、もちろんRubyは大好きな言語です。

なんといってもRubyは書いていて楽しいですよね。さっと書けるし、文字どおりなんでもできる言語なので、思ったとおりに自由にプログラムが書けます。ライブラリもエコシステムも成熟しているので必要な機能をさっと作れる、これも良い部分ですね。

そうは言っても、Rubyにはなかなか難しいところもあります。当たり前のことかもしれませんが、Rubyで書いたプログラムを実行するためには、Rubyインタプリタを実行するマシンにインストールする必要があります。例えばWebブラウザ、モバイルOSなど、制限された環境だと、特にCRubyをインストールするのは難しいです。

というわけで、ユーザーにRubyをインストールさせない場合、たいていサーバーサイドで実行することになると思います。ただこれは、プログラムのユーザーにとっては優しいですが、プログラムの開発者にとっては優しくありません。特にちょっとしたプログラムを公開したいだけの場合、サーバーの管理コストとかは考えたくないですよね。

それからもう1つ大きな難しさとして、Rubyのインストールバトルがあります。Rubyのインストールは初学者が躓きがちなポイントで、環境ごとにトラブルシュートする必要がある難しい問題です。今ではruby-buildなどでかなり簡単になっていますが、それでも「BUILD FAILED」という文字列を何度も見たことがあると思います。

これらの難しさを解消することは、Rubyへの入り口を広げるだけではなく、みなさんの日々のプログラミングライフの中でRubyを使える機会を増やせるんじゃないかなと思います。

Rubyの“難しさ”を解決する「WebAssembly」

さて、ではどうやってこれらの難しさを解消するのがよいでしょうか? その選択肢の1つがWebAssemblyです。WebAssemblyははっきり言ってしまうと、プログラムの配布形式におけるゲームチェンジャーです。

おそらく名前しか聞いたことがないという方もいるので軽く解説すると、WebAssemblyはスタックマシン向けの命令フォーマットの仕様です。これはその名のとおり、Webブラウザでプログラムを動かすことをメインの目的として設計されています。

アーキテクチャやプラットフォームに依存せずにポータブルであること。ソースの言語に非依存であること。第三者から提供された信頼できないプログラムを安全に実行できること。というのがメインのデザインゴールとなっています。

WebAssemblyが何を解決するのかというのを先ほどの課題と照らし合わせてみると、現代では、どの環境にもブラウザがありますよね。Rubyがブラウザで動くようになれば実質的にどこでも動くようになります。ということで、プラットフォームサポートの幅がぐんと広がりますね。

もう1つのインストールの問題ですが、ブラウザ上で動けば明示的なインストールが必要なくなるので、初学者が環境構築で躓くことがぐんと減るのではないかと思います。

WebAssemblyのレイヤーとしては、C、C++、Swiftなどのさまざまな言語からコンパイルされるx86やARMと同様となります。コンパイルされた.wasmのファイルは、Webブラウザ上で実行されます。

具体的には、まずはコンパイラで.wasmファイルを作って、ブラウザ上でJavaScriptを使って、.wasmファイルをダウンロードして、最後にブラウザの実行エンジンでWasmプログラムを起動するという流れになっています。

CRubyを使ってブラウザ上でRubyを動かす

さて、ここでCRubyを使ってRubyをブラウザ上で動かす場合を考えていきましょう。流れとしては、Cで書かれたRubyインタプリタを.wasmファイルにコンパイルして、それをブラウザ上にロードして、起動されたRubyインタプリタでRubyプログラムを実行します。

さて、ここで、どのぐらい手軽にブラウザで動かせるのかをデモしたいと思います。こんな感じでscriptタグを読み込みます。ここでRubyインタプリタがロードされているので、Rubyのスクリプトを書くことができます。「 type="text/ruby"」。なかなかかっこいいですね。

これでRubyを書けるので、とりあえず適当にHTTPサーバーを立てます。実際にブラウザで起動してみると「Hello」が表示されていますね。

(会場拍手)

というわけで、Rubyがブラウザで動いています。「やったね!」ということで。でも「Hello」というワードだけではつまらないですよね。もう少し複雑な、今日の運勢を占ってみましょう。先ほどと同じようにscriptをロードしておいて、とりあえずJavaScriptとの連携が見たいですね。このようにJavaScriptライブラリがあるので、こんな感じでRubyが書けます。

(会場拍手)

JavaScriptとも無事に連携できましたね。ここからおみくじを引いていきたいです。ズルをします。JavaScriptのAPIがそのままRubyのプログラムとして自然に書けます。これはRubyの表現力のおかげですね。これでイベントリスナーが書けて、ブロックでコールバックできるようになったので、ここで今日の運勢をサンプルしてみましょう。結果をDOMに書き込みます。

ということで、なんかエラーが出ていますが……なんて言っているんでしょうか。「undefind、buton」綴りが違うんですね。「t」が足りないと。今度こそ、いけていそうですね。おみくじを引きます。「あ〜、Unlucky」。

(会場笑)

これは何度でも引けるんですよね。「Lucky」になりました。

(会場拍手)

ということで、Rubyでおみくじが書けました。これはおもちゃですよね。もうちょっと複雑なデモをしたいと思います。今のコードは、github.com/ruby/ruby.wasmというリポジトリにあるので、あとでチェックしてみてください。

ブラウザ上でirbを動かす

これが本題ですね。次のデモは、なんとirb(Interactive Ruby)が動きます。これ見えますかね? 普通のirbですね。これはブラウザで動いているんですよ。コンソールじゃないんですよ。ここで時間を表示してみますか。UTCですね。ここ実は、gemがインストールできて、syntax_treeというRubyのフォーマットがあったり、lintだったりできるやつがあるんですけど。

これはピュアRubyで書かれているので、ここのデモにちょうどいいんですよね。なかなかダウンロードが終わらないですね。…本当だ。これだからデモは怖いですね(笑)。今度はなんだ。gemだ、本当だ(笑)。みなさんのほうが詳しいですね。

(会場笑)

ようやくインストールできました。これで……え、今度は何。なんだなんだ。いいや、こっちができたので、じゃあsyntax_treeはいったん置いておいて、SVGのデモをしましょう。これはSVGをRubyで組み立てられるgemで、今適当に書いたんですが、こんな感じでSVGが生成されるわけですね。これはブラウザ上で動いているので、これをそのまま表示してみましょう。

(会場拍手)

よかった。こっちはうまくいった。ということで、irbがだいたい動きます。こちらはインターネットに公開しているので、ぜひお気に入りのgemで試してみてください。

WebAssemblyはどのように動いているのか?

いかがでしたか? ここからは、今デモしたWebAssemblyがどのように動いているのかを紹介します。

実はバニラなWebAssembly自体には、ファイルシステムへのアクセス方法は既定されていません。システムの時計にもアクセスできませんし、もちろんネットワークにもアクセスできません。では、先ほど見たTime.nowは何を返しているのでしょうか? お察しのとおり、WebAssembly自体でできないことは外側のホスト環境、ここではJavaScriptにやってもらおうという感じになっています。

実際にTime.nowが呼ばれると、CRubyの中でclock_gettimeシステムコールが呼ばれて、最終的にData.nowが呼ばれて結果が返ってくるわけですね。

ただ、ここでWebAssemblyが今やWebだけのものではなくなってきているという話が出てきます。sandboxによるセキュリティ機構やバイナリのポータビリティは、Webブラウザ以外の場所でもうれしいですよね。

例えば、サーバーレスプラットフォームはだいたい関数のロジックをデプロイするわけですが、プラットフォーム側がWasmさえサポートしていれば、ユーザーはいろいろな言語でロジックを書けるわけです。これはプラグインシステムなどでも同じようなうれしさがあると思います。というわけで、WebAssemblyはブラウザのJavaScriptと常に一緒に実行されるわけではないという状況になってきています。

プラットフォームに依存しないシステムコールインターフェイスを標準化するWASI

そこで、WASIという標準化されたシステムコールの仕様が出てきました。WASIは、WebAssembly System Interfaceの略で、プラットフォームに非依存なシステムコールインターフェイスを定義します。システムスタックとしては、MuslなどのlibcがWASIのインターフェイスを通して実際のWASIの実装を呼び出します。

ここ数年、Node.js、Deno、WasmをCLIから実行するWasmtime、サーバーレス環境であるFastlyのCompute@EdgeやCloudflare Workers。また最近は、VSCodeの拡張機能がWASI対応をしたり、ログ収集ツールのFluent BitのプラグインシステムがWASIをサポートしたりしています。

また、言語側でもサポートが広がっています。CやC++、Rust、Swiftなどがサポートしていて、ここに今回Rubyが加わりました。ただ、他の言語とちょっと事情が違うのが、RubyはRubyインタプリタと.rbファイルを配布しないといけません。

あとは、そもそもWasmに対応しているブラウザ以外の環境だと、だいたいOne Binaryしか受け付けてくれないんですよね。「.rbファイルを別途ホストのファイルシステムに配置してから起動してくださいね」というのはなかなか面倒ですよね。というわけで、ホストから読み取っていた.rbファイルを、こうしてバイナリに埋め込むためのwasi-vfsという仮想ファイルシステムを開発しました。とりあえずRubyを動かすためにRead-onlyなインメモリのファイルシステムをサポートしています。

このVFSは、WASIのインターフェイスをプロキシする感じで動くので、実はRuby以外でも使えます。風の噂でCPythonがこれで使えたと聞きました。

RubyをCompute@Edgeで動かす

ブラウザ以外で動く例として、サーバーレスのデモをしたいと思います。先ほど名前を出したFastlyのCompute@Edgeで動くデモを作ってきました。

今回のスクリプトはこんな感じです。FastlyのCompute@Edgeは、HTTPリクエストをCDNのEdgeサーバーでハンドルするサービスなので、リクエストを受け取ってレスポンスを返すロジックを書いていきます。

リクエストを受けるとWasmファイルは実行されるので、トップレベルにレスポンスを組み立てるロジックを書いていきます。これは単純にHTMLを組み立てて、現在の時間をTime.nowでWASIのインターフェイスを通して取得して、あとはRubyのバージョンや環境を書き出して、クライアントに送ります。

この.rbファイルをこんな感じで.wasmファイルに埋め込みます。この.wasmファイルにはRubyインタプリタと.rbファイルが含まれているわけですね。

今のスクリプトをデプロイした環境がこちらです。アクセスしてみてください。というわけでRubyがEdgeで動きました。

(会場拍手)

でもこれはクライアントと見分けがつかないですよね(笑)。

(会場笑)

リロードすると時間が増えます。楽しい。という感じです。

内容に戻ると、Ruby3.2ではWebAssemblyとWASIのサポートが入ります。ポートしたRubyがむき出しの状態だとみなさん使いにくいと思うので、ruby.wasmというリポジトリで、npmパッケージとビルド済みのWasmファイルを配布しています。実は先ほどのデモも、このnpmパッケージを使ってちょちょいと作っていました。みなさんぜひお気軽にお試しください。

(次回へつづく)

続きを読むには会員登録
(無料)が必要です。

会員登録していただくと、すべての記事が制限なく閲覧でき、
著者フォローや記事の保存機能など、便利な機能がご利用いただけます。

無料会員登録

会員の方はこちら

関連タグ:

この記事のスピーカー

同じログの記事

コミュニティ情報

Brand Topics

Brand Topics

  • 1年足らずでエンジニアの生産性が10%改善した、AIツールの全社導入 27年間右肩上がりのサイバーエージェントが成長し続ける秘訣

人気の記事

新着イベント

ログミーBusinessに
記事掲載しませんか?

イベント・インタビュー・対談 etc.

“編集しない編集”で、
スピーカーの「意図をそのまま」お届け!