RubyでWebAssemblyを試してみよう #ruby #WebAssembly #WASM #WASI
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
WebAssembly (WASM)を使うと、ウェブブラウザ上でJavaScript以外の言語を動作させることができます。過去記事にPythonを動作させた例もありました。本記事ではRubyを動かしてみます。
ruby.wasm
RubyでのWASMには、そのものずばりな名称の ruby.wasm があります。これを使ってブラウザ上で簡単なRubyスクリプトを動かしてみましょう。
puts "Hello, world!"
おなじみ Hello, world! を出力するだけのRubyスクリプトです。これをブラウザ上で動かすには Quick Example: Ruby on browser に従い、次のHTMLファイルを作成します。
<script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@1.0.1/dist/browser.script.iife.js"></script> <script type="text/ruby"><br /> puts "Hello, world!"<br /> </script>
このファイルをGoogle ChromeやFirefoxで開いてみると…何も起こりません。ブランクページが開かれるだけです。何か間違っているのでしょうか? ここでGoogle Chromeならデベロッパーツール、Firefoxならウェブ開発ツールを開き、コンソールを見てみましょう。
Chrome:
Firefox:
このように、ブラウザのウィンドウ内ではなく、コンソールに Hello, world! が出力されていました!
次はブラウザのウィンドウ内に Hello, world! を出力してみましょう。 ruby.wasmのJSモジュール を利用することで、Rubyスクリプト中からJavaScriptを操作できます。
<script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@1.0.1/dist/browser.script.iife.js"></script> <script type="text/ruby"><br /> require 'js'<br /> JS.global[:document].call(:write, "Hello, world!")<br /> </script>
<script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@1.0.1/dist/browser.script.iife.js"></script> <script type="text/ruby"><br /> require 'js'<br /> message = JS.global[:document].call(:getElementById, 'message')<br /> message[:innerText] = "Hello, world!"<br /> </script> <div id="message"></div>
このどちらも、ブラウザのウィンドウ内に Hello, world! を描画します。
WASI (WebAssembly System Interface)
WASI (WebAssembly System Interface)とは簡単に言うと、WASMをブラウザ外で動作させるための仕組みです。WASIは「コンテナの次」となるポータブルでセキュアな仕組みとして、にわかに注目を集めています。ここからはRubyスクリプトを WASI で動かしてみましょう。
ruby.wasi 公式ページには Quick Example: How to package your Ruby application as a WASI application として、おなじみ Hello, world! を出力するだけのRubyスクリプトを WASI アプリケーションに変換して動かす方法が記載されています。これに従って進めていきましょう。
まず、Hello, world! を出力するだけのRubyスクリプトを作成します。
% mkdir src % echo 'puts "Hello, world!"' > src/hello.rb % ruby -v ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux-gnu] % ruby src/hello.rb Hello, world! %
このように x86_64-linux 上で動作しています。
次に wasi-vfs を用意します。これはRubyスクリプトを WASI アプリケーションに変換する際に利用します。
% export WASI_VFS_VERSION=0.2.0 % curl -LO "https://github.com/kateinoigakukun/wasi-vfs/releases/download/v${WASI_VFS_VERSION}/wasi-vfs-cli-x86_64-unknown-linux-gnu.zip" % unzip wasi-vfs-cli-x86_64-unknown-linux-gnu.zip % ./wasi-vfs wasi-vfs-cli 0.2.0 USAGE: wasi-vfs FLAGS: -h, --help Prints help information -V, --version Prints version information SUBCOMMANDS: help Prints this message or the help of the given subcommand(s) pack Package directories into Wasm module
そして ruby.wasm の WASI 互換環境を準備します。
% curl -LO https://github.com/ruby/ruby.wasm/releases/latest/download/ruby-3_2-wasm32-unknown-wasi-full.tar.gz % tar xf ruby-3_2-wasm32-unknown-wasi-full.tar.gz % ls ./3_2-wasm32-unknown-wasi-full/usr/local/bin/ bundle bundler erb gem irb racc rake rbs rdbg rdoc ri ruby typeprof %
この ruby は WASI 互換環境なので Linux 上では実行できません。
% ./3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby zsh: 実行形式エラー: ./3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby % file ./3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby ./3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby: WebAssembly (wasm) binary module version 0x1 (MVP) %
ここで Wasmtime を準備しましょう。これは WASI アプリケーションを実行できるスタンドアローンランタイムです。
% curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/v7.0.0/wasmtime-v7.0.0-x86_64-linux.tar.xz % tar xf wasmtime-v7.0.0-x86_64-linux.tar.xz % ./wasmtime-v7.0.0-x86_64-linux/wasmtime 3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby -- -v ruby 3.2.0 (2022-12-25 revision a528908271) [wasm32-wasi] %
このように wasmtime を噛ませることによって、 WASI 互換環境の ruby を実行することができました。
では、wasi-vfs と WASI 互換環境の ruby を使って、Rubyスクリプトを WASI アプリケーションに変換してみましょう。
% ./wasi-vfs pack ./3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby --mapdir /src::./src --mapdir /usr::./3_2-wasm32-unknown-wasi-full/usr -o hello.wasm % file ./hello.wasm ./hello.wasm: WebAssembly (wasm) binary module version 0x1 (MVP) %
Hello, world! を出力する WASI アプリケーションを wasmtime で実行してみます。
% ./wasmtime-v7.0.0-x86_64-linux/wasmtime hello.wasm -- /src/hello.rb Hello, world! %
このようにHello, world!を出力できました。が、「 -- /src/hello.rb」のように WASM パッケージ内で実行するスクリプトを引数に指定する必要があるので、本当に WASI アプリケーションとして動いているの? ローカルファイルを実行しているんじゃないの? という疑問があるでしょう。
% echo "puts RUBY_DESCRIPTION" >> src/hello.rb % ruby ./src/hello.rb Hello, world! ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux-gnu] % ./wasmtime-v7.0.0-x86_64-linux/wasmtime hello.wasm -- /src/hello.rb Hello, world! %
ソースコードを変更して実行しても反映されていないので、どうやら実際に WASI アプリケーションが動作しているようです。もう一度ビルドして確認してみましょう。
% ./wasi-vfs pack ./3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby --mapdir /src::./src --mapdir /usr::./3_2-wasm32-unknown-wasi-full/usr -o hello.wasm % ./wasmtime-v7.0.0-x86_64-linux/wasmtime hello.wasm -- /src/hello.rb Hello, world! ruby 3.2.0 (2022-12-25 revision a528908271) [wasm32-wasi] %
問題なく WASI アプリケーションとして動作していました。
また、wasmtime 以外のWASMスタンドアローンランタイムとして Wasmer があります。こちらも試してみましょう。
% curl -LO https://github.com/wasmerio/wasmer/releases/download/v3.2.0/wasmer-linux-amd64.tar.gz % tar xf wasmer-linux-amd64.tar.gz % ./bin/wasmer hello.wasm -- /src/hello.rb Hello, world! ruby 3.2.0 (2022-12-25 revision a528908271) [wasm32-wasi] %
wasmtime と同様に wasmer でも実行できました。
まとめ
本稿では Ruby で WASM / WASI を試してみました。文中で述べたように、WASIは「コンテナの次」となるポータブルでセキュアな仕組みとして注目を集めています。クリエーションラインでは引き続き WebAssmbly について調査していきたいと思います。