Rubyスクリプトについて、 ruby.wasm を使うことでWasm化できると知りました。
ruby/ruby.wasm: ruby.wasm is a collection of WebAssembly ports of the CRuby.
また、Wasm化したRubyスクリプトはブラウザで動かせるだけではなく、Wasm処理系でも動かせることを知りました。
そこで、ruby.wasm にてRubyスクリプトをWasm化し、Wasm処理系の1つである Wasmtime で動かしてみたことから、メモを残します。
目次
- 環境
- Wasm化後にどの処理系で動かすかを検討
- ruby.wasm をビルドする
- ビルドしたWasmを使って、ローカルのRubyスクリプトを動かす
- WasmにRubyだけで書かれたgemを追加して動かす
- Wasmへgemに加えてRubyスクリプトも同梱して動かす
- その他参考資料
- ソースコード
環境
- WSL2
- Ruby 3.3.6
- ruby.wasm 2.7.0
- Wasmtime 28.0.0
- mise 2024.12.20 linux-x64 (1fae2b0 2024-12-25)
- RubyとWasmtimeのバージョン切り替えのために
miseを使っています
- RubyとWasmtimeのバージョン切り替えのために
ちなみに、bundle config の path は vendor/bundle に設定済だとします。
$ bundle config path Set for the current user (/home/user/.bundle/config): "vendor/bundle"
Wasm化後にどの処理系で動かすかを検討
ブラウザ以外でWasmを動かす場合、いくつかの処理系を選べそうでした。
- Wasmtime
- WasmEdge
- Wasmer
将来のことを考えて、処理系のバージョンを切り替えられると良さそうと感じたことから、 mise の registry を見たところ、 Wasmtime と Wasmer がありました。
Registry | mise-en-place
今回処理速度は気にしないこと、Wasmの事実上の参照実装らしいことから、 Wasmtime を使うことにしました。
WebAssemblyランタイム「Wasmtime」がバージョン1.0に到達、本番利用に対応。Bytecode Allianceによる事実上の参照実装 - Publickey
ruby.wasm をビルドする
ruby.wasmを使おうとREADMEを見たところ、
などが選べそうでした。
RubyKaigi 2024のスライドを見たところ、 ruby_wasm gemをインストールして rbwasm build コマンドを使えばビルドできそうでした。
RubyGems on ruby.wasm - Speaker Deck
そこで今回は自分でビルドしてみることにしました。
まずはGemfileを作ります。
$ bundle init Writing new Gemfile to /path/to/wasm_file_writer/Gemfile
続いて bundle add します。
$ bundle add ruby_wasm --group=development ... Installing ruby_wasm 2.7.0 (x86_64-linux)
続いて、 rbwasm コマンドに -o オプションを追加して ruby.wasm ファイルをビルドします。なお、今回はRuby3.3系を使うので、 --ruby-version は不要です。
rbwasm コマンドには色々なオプションがあることから、bundle exec rbwasm build --help で確認しておくのもよさそうです。
ちなみに、初回のビルドは時間がかかります。
$ bundle exec rbwasm build -o ruby.wasm ... INFO: Packaging setup.rb: bundle/setup.rb INFO: Size: 51.11 MB
ビルドが終わったら、Wasmtimeを使ってWasm化したRubyのバージョンを確認してみます。
$ wasmtime run ruby.wasm --version ruby 3.3.3 (2024-06-12 revision f1c7b6f435) [wasm32-wasi]
WasmのRubyは、ローカルのRubyとはバージョンが異なるようです。
$ ruby -v ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [x86_64-linux]
RubyKaigi 2024のスライドのコードを使ってみたところ、Bundlerで追加されたgemはなさそうです。
$ wasmtime run ruby.wasm -e 'puts Dir.glob(["/usr/local/*", "/bundle/*/*"])' /usr/local/bin /usr/local/lib /usr/local/share
ビルドしたWasmを使って、ローカルのRubyスクリプトを動かす
今回、ローカルのRubyスクリプトは src ディレクトリに入れておくことにします。
Hello world
まずは Hello world! を出力するRubyスクリプト hello_world.rb です。
puts 'Hello, world!'
続いてこのスクリプトをWasm化したRubyで動かしてみます。
この際、デフォルトだとローカルのファイルは見えないことから、Wasmtimeの --dir オプションを使って、ローカルの ./src をWasmの / に紐づけます。
wasmtimeコマンドのTips
実行結果は以下で、動きました。
$ wasmtime run --dir ./src::/ ruby.wasm hello_world.rb Hello, world!
コマンドラインから値を渡す
次はコマンドラインから値を渡す hello_args.rb です。
puts "Hello, #{ARGV[0]}!"
こちらも問題なく動作しました。
$ wasmtime run --dir ./src::/ ruby.wasm hello_args.rb Akibae Hello, Akibae!
ファイルへの書き込み
続いてファイルへ書き込む hello_file.rb です。
puts Dir.pwd File.open('hello_file', 'w', 0755) { |f| f.puts 'Hello, file!' }
実行すると、Wasm上での Dir.pwd 結果として / が表示されました。
$ wasmtime run --dir ./src::/ ruby.wasm hello_file.rb /
ファイルも作成されました。
$ tree -L 2 . ├── src │ ├── hello_args.rb │ ├── hello_file │ ├── hello_file.rb │ └── hello_world.rb ...
作成されたファイルの中身も想定通りです。
$ cat src/hello_file Hello, file!
WasmにRubyだけで書かれたgemを追加して動かす
RubyKaigi 2024のスライドによると、ruby.wasmではgemを追加して動かすこともできるようでした。
RubyGems on ruby.wasm - Speaker Deck
今回は Ruby だけで書かれたgemを追加して動かしてみます。
Gemfileを編集してビルドする
まずはGemfileの末尾にgemを追加します。
今回追加するgemは以下の通りで、いずれもファイルへの書き込みを行います。
Gemfileの様子はこちら。
# RubyGemsにアップロードしてあるgem gem "thinkami_hello_file_writer" # GitHubだけにあるgem gem "hello_file_writer", git: 'https://github.com/thinkAmi-sandbox/hello_file_writer_ruby', branch: 'main'
ローカルに bundle install しておきます。
$ bundle install Fetching https://github.com/thinkAmi-sandbox/hello_file_writer_ruby Fetching gem metadata from https://rubygems.org/. Resolving dependencies... Fetching thinkami_hello_file_writer 0.1.0 Installing thinkami_hello_file_writer 0.1.0 Bundle complete! 3 Gemfile dependencies, 4 gems now installed. Bundled gems are installed into `./vendor/bundle`
続いて、 rbwasmコマンドでビルドし ruby_with_gem.wasm というWasmを作成します。今回は高速で終わりました。
$ bundle exec rbwasm build -o ruby_with_gem.wasm ... INFO: Packaging gem: hello_file_writer-0.1.0 INFO: Packaging gem: thinkami_hello_file_writer-0.1.0 INFO: Packaging setup.rb: bundle/setup.rb INFO: Size: 51.16 MB
ruby_with_gem.wasmにあるgemを確認します。Gemfileへ追加したgemが /bundle の下に増えていました。
$ wasmtime run ruby_with_gem.wasm -e 'puts Dir.glob(["/usr/local/*", "/bundle/*/*"])' /usr/local/bin /usr/local/lib /usr/local/share /bundle/gems/hello_file_writer-0.1.0 /bundle/gems/thinkami_hello_file_writer-0.1.0
Wasmに追加されたgemを使うRubyスクリプトを実行
WasmのgemをRubyスクリプトの中で使う方法を調べたところ
require 'bundler/setup'require '/bundle/setup'
のどちらかを使えば良さそうでした。
今回の環境の場合、前者にすると
Could not find ruby_wasm-2.7.0 in locally installed gems
のようなメッセージが出て動作しませんでした。
そこで、後者の require '/bundle/setup' を使うことにします。
require '/bundle/setup' require 'hello_file_writer' require 'thinkami_hello_file_writer' puts('By RubyGems =====>') ThinkamiHelloFileWriter.write_file puts('By GitHub =====>') HelloFileWriter.write_file
このスクリプトも問題なく動作しました。
$ wasmtime run --dir ./src::/ ruby_with_gem.wasm hello_gem.rb By RubyGems =====> / By GitHub =====> Hello GitHub
ファイルの中身も想定通りでした。
$ cat src/file_by_gem open and write by gem. $ cat src/file_by_github_gem open and write by GitHub gem
Wasmへgemに加えてRubyスクリプトも同梱して動かす
ruby.wasm ではWasmにRubyスクリプトも同梱できる rbwasm pack コマンドも用意されていますので、試してみます。
rbwasmでpackする
まずは pack します。buildのときと異なり、ローカルの ./src はWasmの /src に入れておきます。
$ bundle exec rbwasm pack ruby_with_gem.wasm --dir ./src::/src -o package_with_gem.wasm
rbwasm pack の実行結果は特に表示されません。そこで、Wasmtimeで Dir.glob してファイルが増えているか確認します。良さそうです。
$ wasmtime run package_with_gem.wasm -e 'puts Dir.glob(["/usr/local/*", "/bundle/*/*", "/src/*"])' /usr/local/bin /usr/local/lib /usr/local/share /bundle/gems/hello_file_writer-0.1.0 /bundle/gems/thinkami_hello_file_writer-0.1.0 /src/hello_args.rb /src/hello_file.rb /src/hello_gem.rb /src/hello_world.rb
WasmtimeでWasm内のRubyスクリプトを実行
Hello worldなスクリプト
続いて、WasmtimeでWasmの中にあるRubyスクリプトを実行します。
まずは hello_world.rb です。Wasmの外にあるファイルと異なり、 --dir は不要で実行できます。
$ wasmtime run package_with_gem.wasm /src/hello_world.rb Hello, world!
ファイル書き込みがあるスクリプト
ファイル書き込みがあるスクリプトを実行する場合、何もしないとエラーになります。
$ wasmtime run package_with_gem.wasm /src/hello_gem.rb
By RubyGems =====>
/
/bundle/gems/thinkami_hello_file_writer-0.1.0/lib/thinkami_hello_file_writer.rb:10:in `initialize': No such file or directory @ rb_sysopen - file_by_gem (Errno::ENOENT)
from /bundle/gems/thinkami_hello_file_writer-0.1.0/lib/thinkami_hello_file_writer.rb:10:in `open'
from /bundle/gems/thinkami_hello_file_writer-0.1.0/lib/thinkami_hello_file_writer.rb:10:in `write_file'
from /src/hello_gem.rb:6:in `<main>'
エラーを見ると、Wasm内での Dir.pwd 実行結果は / と分かりました。
そこで、ローカルの . をWasmの / にマッピングして実行します。
$ wasmtime run --dir .::/ package_with_gem.wasm /src/hello_gem.rb By RubyGems =====> / By GitHub =====> Hello GitHub
ファイルもできていました。
$ cat file_by_gem open and write by gem. $ cat file_by_github_gem open and write by GitHub gem
以上で、ruby.wasm + Wasmtimeにて動作することを確認できました。
その他参考資料
ソースコード
GitHubに上げました。参考までに今回作成したWasmファイルも上げてありますが、利用は個人の判断におまかせします。
https://github.com/thinkAmi-sandbox/ruby_wasm_file_writer-example