Wasmer logo

Join the Wasmer Community License

The Ruby extension to run WebAssembly

The goal of the project is to be able to run WebAssembly binaries from Ruby directly. So much fun coming!

Under Development, don't use it in product yet.

What is WebAssembly?

Quoting the WebAssembly site:

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.

About speed:

WebAssembly aims to execute at native speed by taking advantage of common hardware capabilities available on a wide range of platforms.

About safety:

WebAssembly describes a memory-safe, sandboxed execution environment […].


There is a toy program in examples/simple.rs, written in Rust (or any other language that compiles to Wasm):

pub extern fn sum(x: i32, y: i32) -> i32 {
    x + y

Once this program compiled to WebAssembly, we end up with a examples/simple.wasm binary file.

Then, we can execute it in Ruby (!) with the examples/simple.rb file:

require "wasmer"

bytes = IO.read "simple.wasm", mode: "rb"
instance = Instance.new bytes
puts instance.exports.sum 1, 2

And then, finally, enjoy by running:

$ ruby simple.rb

API documentation

The Instance class

Instantiates a WebAssembly module represented by bytes, and calls exported functions on it:

require "wasmer"

# Get the Wasm module as bytes.
wasm_bytes = IO.read "my_program.wasm", mode: "rb"

# Instantiates the Wasm module.
instance = Instance.new wasm_bytes

# Call a function on it.
result = instance.exports.sum 1, 2

puts result # 3

All exported functions are accessible on the exports getter. Arguments of these functions are automatically casted to WebAssembly values.

The memory getter exposes the Memory class representing the memory of that particular instance, e.g.:

view = instance.memory.uint8_view

See below for more information.

The Memory class

A WebAssembly instance has its own memory, represented by the Memory class. It is accessible by the Instance.memory getter.

The Memory class offers methods to create views of the memory internal buffer, e.g. uint8_view, int8_view, uint16_view etc. All these methods accept one optional argument: offset, to subset the memory buffer at a particular offset. These methods return respectively a *Array object, i.e. uint8_view returns a Uint8Array object etc.

offset = 7
view = instance.memory.uint8_view offset

puts view[0]

The *Array classes

These classes represent views over a memory buffer of an instance.

Class View buffer as a sequence of… Bytes per element
Int8Array int8 1
Uint8Array uint8 1
Int16Array int16 2
Uint16Array uint16 2
Int32Array int32 4
Uint32Array uint32 4

All these classes share the same implementation. Taking the example of Uint8Array, the class looks like this:

class Uint8Array
    def bytes_per_element
    def length
    def [](index)
    def []=(index, value)

Let's see it in action:

require "wasmer"

# Get the Wasm module as bytes.
wasm_bytes = IO.read "my_program.wasm", mode: "rb"

# Instantiates the Wasm module.
instance = Instance.new wasm_bytes

# Call a function that returns a pointer to a string for instance.
pointer = instance.exports.return_string

# Get the memory view, with the offset set to `pointer` (default is 0).
memory = instance.memory.uint8_view pointer

# Read the string pointed by the pointer.
nth = 0
string = ""

while true
  char = memory[nth]

  if 0 == char

  string += char.chr
  nth += 1

puts string # Hello, World!

Notice that *Array treat bytes in little-endian, as required by the WebAssembly specification, Chapter Structure, Section Instructions, Sub-Section Memory Instructions:

All values are read and written in little endian byte order.

Each view shares the same memory buffer internally. Let's have some fun:

int8 = instance.memory.int8_view
int16 = instance.memory.int16_view
int32 = instance.memory.int32_view

int8[0] = 0b00000001
int8[1] = 0b00000100
int8[2] = 0b00010000
int8[3] = 0b01000000

// No surprise with the following assertions.
assert_equal 0b00000001, int8[0]
assert_equal 0b00000100, int8[1]
assert_equal 0b00010000, int8[2]
assert_equal 0b01000000, int8[3]

// The `int16` view reads 2 bytes.
                  b₂       b₁
               ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
assert_equal 0b00000100_00000001, int16[0]
                  b₄       b₃
               ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
assert_equal 0b01000000_00010000, int16[1]

// The `int32` view reads 4 bytes.
                  b₄       b₃       b₂       b₁
               ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
assert_equal 0b01000000_00010000_00000100_00000001, int32[0]

The Module class

The Module class contains one static method validate, that checks whether the given bytes represent valid WebAssembly bytes:

require "wasmer"

wasm_bytes = IO.read "my_program.wasm", mode: "rb"

if not Module.validate wasm_bytes
    puts "The program seems corrupted."

This function returns a boolean.

Install and Testing

To compile the entire project, run the following commands:

$ just build
$ just test
$ ruby examples/simple.rb

(Yes, you need just).


The entire project is under the BSD-3-Clause license. Please read the LICENSE file.