📝

Ruby スクリプトを wasmtime 上で走らせるまで

2023/09/06に公開

TL;DR;

次のステップで Ruby スクリプトを wasmtime 上で動かせます:

  1. wasmtime をインストールします
  2. WASI 対応 ruby.wasm をダウンロードします
  3. プロジェクトフォルダを作ります
  4. 仮装環境と実環境の対応を設定し、wasmtime を起動します

背景

  • CRuby がベースの Wasm 化された Ruby 処理系 ruby.wasm が存在します
  • WASI preview 1 に対応した ruby.wasm もリリースされています
  • Wasm 処理系の wasmtime には CLI が存在し、コマンドライン上で WASI preview 1 に対応した Wasm モジュールを実行できます

実行の手順

上述の通り、次の手順で Ruby スクリプトを wasmtime 上で動作する Ruby 処理系で実行できます。

  1. wasmtime をインストールします。
  2. WASI 対応 ruby.wasm をダウンロードします。
  3. プロジェクトフォルダを作ります。
  4. 仮装環境と実環境の対応を設定し、wasmtime を起動します。

wasmtime をインストール

wamtime に書かれている通りに wasmtime の CLI をインストールします。

% curl https://wasmtime.dev/install.sh -sSf | bash

WASI 対応 ruby.wasm をダウンロード

GitHub からリリースバージョンをダウンロードします。今回は ruby-3_2-wasm32-unknown-wasi-full.tar.gz を利用します。

プロジェクトフォルダを作成する

プロジェクト用のフォルダを作成します。次の例では hello-ruby-wasm というフォルダを作成しています。

% mkdir hello-ruby-wasm

プロジェクトフォルダ内に lib フォルダも作成します

% mkdir -p hello-ruby-wasm/lib

ruby.wasm の展開

プロジェクトフォルダ内に、ダウンロードした ruby.wasm を展開します。次の例では ~/Download にダウンロードしたファイルを展開しています。

% cd hello-ruby-wasm
% tar zxf ~/Download/ruby-3_2-wasm32-unknown-wasi-full.tar.gz

現在のディレクトリ構成は次のようになっています:

% tree -L 3
.
├── 3_2-wasm32-unknown-wasi-full
│  └── usr
│     └── local
└── lib

ruby.wasm の動作確認

Ruby 処理系を起動し、動作することを確認します。3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby が実行する Wasm 化された Ruby 処理系です。

% wasmtime 3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby -- --version
ruby 3.2.0 (2022-12-25 revision a528908271) [wasm32-wasi]

-- を引数に指定し忘れると次のようなエラーが出るので注意してください。-- は wasmtime 用のコマンドライン引数と、Ruby 処理系に用のコマンドライン引数の区切りを表します。-- がないと --version オプションも wasmtime 向けの引数であると解釈されるため、エラーが表示されます。

% wasmtime 3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby --version
error: unexpected argument '--version' found

  tip: to pass '--version' as a value, use '-- --version'

Usage: wasmtime <MODULE> [ARGS]...

hello.rb を作成

スクリプトを作成します。次のような hello world スクリプトを lib フォルダ内に作ります。

puts "content-type: text/plain"
puts ""
puts "Hello, World"

プロジェクトフォルダは次のようになっています。

% tree -L 3
.
├── 3_2-wasm32-unknown-wasi-full
│  └── usr
│     └── local
└── lib
   └── hello.rb

ネイティブ版の Ruby 処理系で実行すると、次のように出力されます:

% ruby lib/hello.rb
content-type: text/plain

Hello, World

wasmtime 上で実行

ただ wasmtime で実行すると、次のようなエラーが表示されます。これは wasmtime がサンドボックスの中で Wasm モジュールを実行するため、必要なファイルへアクセスできないことが原因です。

% wasmtime 3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby -- lib/hello.rb
ruby: Capabilities insufficient -- lib/hello.rb (LoadError)

wasmtime が lib/hello.rb へアクセスするには、次のように--dir オプションでプロジェクトフォルダへのアクセスを許可します。

% wasmtime --dir . 3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby -- lib/hello.rb
`RubyGems' were not loaded.
`error_highlight' was not loaded.
`did_you_mean' was not loaded.
`syntax_suggest' was not loaded.
content-type: text/plain

Hello, World

上記の例ではスクリプトが実行されていますが、必要なライブラリーがロードできないためエラーが発生しています。

ruby.wasm は実行に必要なファイルが /usr 以下からも探します。サンドボックスの中から /usr へアクセスできません。

wasmtime にはフォルダのマッピング機能があります。これは wasmtime の実行している環境(ホスト環境と呼びます)のフォルダを、別のフォルダとして実行されている Wasm モジュールにアクセスさせられます。

例えば、ホスト環境の ./root-for-wasm フォルダを、Wasm の実行環境(ゲスト環境と呼びます)のルートフォルダ(/)とすることが可能です。

この機能を使ってプロジェクトフォルダ内にある3_2-wasm32-unknown-wasi-full/usr フォルダを、ゲスト環境の /usr と設定します。

設定したコマンドの例は次のようになります:

% wasmtime --dir . --mapdir /usr::./3_2-wasm32-unknown-wasi-full/usr  3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby -- lib/hello.rb
content-type: text/plain

Hello, World

--mapdir オプジョンがフォルダの対応関係を指定するオプションです。:: の前がゲスト環境でのパス、後がホスト環境でのパスになります。

まとめ

「Wasm はサンドボックスの中で実行される」とはよく聞きますが、それを実感できる内容だったように思います。

標準出力を利用する Wasm モジュールの実行が何の障害もなく行えたのも、WASI への対応が成熟してきていることを実感できました。

レファレンス

  • Ruby in WebAssembly:WASM モジュールが実行できるサーバサイド実行環境 Spin で ruby.wasm を利用するチュートリアルです

Discussion