Ruvy: A Toolchain for Pre-Initialized Ruby in WebAssembly

Shopify has open sourced Ruvy, a toolchain that compiles Ruby code into a WebAssembly (Wasm) module. The module executes the original Ruby script when its exported _start function is called. Ruvy builds on top of ruby.wasm but adds two significant improvements: pre-initializing the Ruby virtual machine at build time and eliminating the need for WASI arguments at runtime.

WASI (WebAssembly System Interface) is a standardized set of imported functions that provides Wasm modules with access to system-level operations like file reads, environment variables, and time retrieval. WASI arguments are conceptually similar to command-line arguments: code compiled to WASI reads them through the WASI API, just as native code would read argv.

Building and Running a Module

Ruvy does not currently ship precompiled binaries, so users must install its build dependencies and compile it first. Instructions are in the project's README. Once built, invocation is straightforward:

The input file ruby_examples/hello_world.rb simply contains puts "Hello world". The CLI produces index.wasm, which prints that string when its _start function is executed.

For programs that span multiple Ruby files, Ruvy accepts a --preload flag pointing to a directory of additional files. Every file in that directory is loaded into the Ruby VM at build time, making its definitions available to the main script. This means the Wasm module is fully self-contained and does not need a separate filesystem at runtime.

How Ruvy Differs from ruby.wasm

ruby.wasm is a collection of CRuby ports to WebAssembly, targeting both browser environments via Emscripten and non-browser environments via WASI. The WASI ports include the Ruby interpreter itself compiled to a Wasm module, but for practical use the interpreter needs access to a filesystem to load Ruby scripts.

One common approach uses wasi-vfs, a tool that packs specified directories into the Wasm module at build time. This avoids shipping Ruby files separately, but the resulting module still expects the path to the entry script as a WASI argument. A typical invocation with a runtime like Wasmtime looks like wasmtime run module.wasm -- /src/my_app.rb.

Pre-initialization Performance Gains

The key architectural difference is when the Ruby VM starts. With a ruby.wasm module built via wasi-vfs, the VM initializes at runtime, when the module executes. Ruvy instead pre-initializes the VM during the Wasm build itself, producing a module that starts faster. Reported benchmarks show around a 20% improvement in instantiation and _start execution time using Wasmtime:

Description

Toolchain

Low

Mid

High

Hello world

Ruby.wasm + wasi-vfs

55.833 ms

56.262 ms

56.730 ms

Ruvy

44.367 ms

44.543 ms

44.739 ms

Includes + logic

Ruby.wasm + wasi-vfs

56.081 ms

56.487 ms

56.932 ms

Ruvy

44.449 ms

44.763 ms

45.216 ms

The "Hello world" case is a trivial puts; the "Includes + logic" case involves a required file defining a class that transforms some input. Ruvy also reduces Wasm-to-native compilation time under Cranelift by approximately 70%:

Description

Toolchain

Low

Mid

High

Hello world

Ruby.wasm + wasi-vfs

1.6351 s

1.6590 s

1.6844 s

Ruvy

439.93 ms

446.31 ms

452.81 ms

Includes + logic

Ruby.wasm + wasi-vfs

1.6227 s

1.6460 s

1.6706 s

Ruvy

442.83 ms

449.40 ms

456.39 ms

These compilation savings stem from the smaller, pre-processed state of the Ruvy module.

No Runtime Arguments Required

Because Ruvy modules carry all necessary Ruby files inside the VM snapshot, they do not require the script path as a WASI argument at execution time. This makes them usable in constrained computing environments, such as edge services, where the runtime configuration cannot supply additional WASI arguments to a _start function.

Why Open Source Ruvy

Shopify released Ruvy to give the broader developer community a simple way to build and run small Ruby programs in WebAssembly runtimes. The README lists several areas where external contributions would be welcome. In particular, Shopify Partners looking to reuse Ruby logic from Shopify Scripts within Shopify Functions may want to examine the open compatibility items for that use case.