以下の内容はhttps://thinkami.hatenablog.com/entry/2024/12/28/231213より取得しました。


ruby.wasmにてRubyスクリプトをWasm化し、Wasmtimeで動かしてみた

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 で動かしてみたことから、メモを残します。

 
目次

 

環境

  • 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 を使っています

ちなみに、bundle config の pathvendor/bundle に設定済だとします。

$ bundle config
path
Set for the current user (/home/user/.bundle/config): "vendor/bundle"

 

Wasm化後にどの処理系で動かすかを検討

ブラウザ以外でWasmを動かす場合、いくつかの処理系を選べそうでした。

 
将来のことを考えて、処理系のバージョンを切り替えられると良さそうと感じたことから、 mise の registry を見たところ、 Wasmtime と Wasmer がありました。
Registry | mise-en-place

今回処理速度は気にしないこと、Wasmの事実上の参照実装らしいことから、 Wasmtime を使うことにしました。
WebAssemblyランタイム「Wasmtime」がバージョン1.0に到達、本番利用に対応。Bytecode Allianceによる事実上の参照実装 - Publickey

 

ruby.wasm をビルドする

ruby.wasmを使おうとREADMEを見たところ、

  • ビルド済の ruby.wasm を利用する
  • 自分で ruby.wasm をビルドする

などが選べそうでした。

 
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スクリプトの中で使う方法を調べたところ

のどちらかを使えば良さそうでした。

今回の環境の場合、前者にすると

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




以上の内容はhttps://thinkami.hatenablog.com/entry/2024/12/28/231213より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14