wasmtime-rb

repository·main·Indexed 40 days ago

https://github.com/bytecodealliance/wasmtime-rb

A Ruby embedding of the Wasmtime runtime (version 48.0.1) that allows Ruby developers to execute WebAssembly modules and components with minimal overhead. It provides APIs for engine configuration, AoT compilation, and the WebAssembly component model, including type mapping between Wasm and Ruby objects.

Tokens
11.6K
Snippets
51
Records
67
Agent score
47%

What's inside wasmtime-rb

  1. Configure Bundler for precompiled gems

    main

    To avoid slow and unreliable source compilations, use the precompiled gems for Linux, macOS, and Windows. When using Bundler, you should lock your bundle to the specific platforms you intend to deploy to using bundle lock --add-platform.

    Supported platforms include:

    • x86_64-linux: Standard Linux (e.g., Heroku, GitHub Actions)
    • x86_64-linux-musl: MUSL Linux (e.g., Alpine Linux)
    • aarch64-linux: ARM64 Linux (e.g., AWS Graviton2)
    • x86_64-darwin: Intel MacOS
    • arm64-darwin: Apple Silicon MacOS (e.g., M1)
    bundle lock --add-platform x86_64-linux
    bundle lock --add-platform x86_64-linux-musl
    bundle lock --add-platform aarch64-linux
    bundle lock --add-platform x86_64-darwin
    bundle lock --add-platform arm64-darwin
  2. Configure pooling allocation with Wasmtime::PoolingAllocationConfig

    main

    The Wasmtime::PoolingAllocationConfig class provides configuration options used with an engine's allocation_strategy to modify the behavior of the pooling instance allocator. This is used to tune memory, tables, stacks, and instance limits for the Wasmtime runtime.

    You can create a new configuration object using Wasmtime::PoolingAllocationConfig.new and then use setter methods to configure its properties.

    config = Wasmtime::PoolingAllocationConfig.new do |c|
      c.total_memories = 10
      c.max_memory_size = 1024 * 1024
    end
  3. Manage GVL for Wasmtime::Func

    main

    When calling Wasm functions, you can control how the Ruby Global VM Lock (GVL) is handled. By default, Wasmtime::Func operations hold the GVL.

    Use without_gvl to obtain a version of the function that releases the GVL during execution. This is useful for long-running Wasm computations that you want to allow other Ruby threads to run concurrently.

  4. How Linker and LinkerInstance work together

    main

    The Linker manages the global state of imports. To define imports, you use a block-based API that yields a LinkerInstance.

    LinkerInstance objects are ephemeral builders. They are only valid within the block they are yielded to. Once the block finishes, the instance becomes unusable. This pattern ensures that the hierarchical structure of imports (namespaces) is correctly scoped.

    • Use linker.root { |instance| ... } to define imports at the top level.
    • Use linker.instance("namespace") { |instance| ... } to define imports within a specific namespace.
    • Use instance.instance("sub-namespace") { |nested| ... } to create nested namespaces.
  5. Wasmtime::Component namespace and sub-modules

    main

    The Wasmtime::Component module provides the entry point for component-related functionality. It includes several key classes for interacting with components:

    • Wasmtime::Component::Component: The core WebAssembly component object.
    • Wasmtime::Component::Linker: Used to define imports for instantiating components.
    • Wasmtime::Component::Instance: Represents an instantiated component.
    • Wasmtime::Component::Func: Represents a function within a component.
    • Wasmtime::Component::WasiCommand: Specifically for WASI command components.
  6. Run Wasm modules from Ruby

    main

    The following workflow demonstrates how to initialize a Wasmtime environment, compile a module from WAT (WebAssembly Text format), define a Ruby-based function to be imported by the Wasm module, and execute the module.

    require "wasmtime"
    
    # 1. Create an engine (re-use throughout your program)
    engine = Wasmtime::Engine.new
    
    # 2. Compile a Wasm module from Wasm or WAT
    mod = Wasmtime::Module.new(engine, <<~WAT)
      (module
        (func $hello (import "" "hello"))
        (func (export "run") (call $hello))
      )
    WAT
    
    # 3. Create a store to hold state
    store = Wasmtime::Store.new(engine, {count: 0})
    
    # 4. Define a Wasm function using Ruby code
    func = Wasmtime::Func.new(store, [], []) do |caller|
      puts "Hello from Func!"
      caller.store_data[:count] += 1
      puts "Ran #{caller.store_data[:count]} time(s)"
    end
    
    # 5. Build the Wasm instance by providing imports
    instance = Wasmtime::Instance.new(store, mod, [func])
    
    # 6. Run the exported function
    instance.invoke("run")
    
    # Alternatively, get the export and call it directly:
    # instance.export("run").to_func.call
  7. Handle Wasmtime errors and traps

    main

    When working with wasmtime-rb, errors are organized in a hierarchy under Wasmtime::Error. You should catch specific error types to handle different failure modes:

    • Wasmtime::ResultError: Occurs when a Ruby-backed function fails to convert its return value into Wasm-compatible types.
    • Wasmtime::ConversionError: Occurs when attempting to convert a Wasmtime::Extern to a concrete type fails.
    • Wasmtime::Trap: Occurs when the WebAssembly execution hits a trap (a runtime error). This class includes several specific trap types that can be identified.
    • Wasmtime::WasiExit: Occurs when a WASI program terminates early by calling exit. You can retrieve the exit code from this error.
  8. Create a new Wasmtime::Memory

    main

    You can create a new WebAssembly memory instance by calling Wasmtime::Memory.new. This requires a Store instance and optional keyword arguments for memory limits. Memory limits are defined in terms of pages.

    Arguments:

    • store: A Wasmtime::Store instance.
    • min_size:: (Required) The minimum number of memory pages.
    • max_size:: (Optional) The maximum number of memory pages.
    # Example of creating a memory with 10 minimum pages and 100 maximum pages
    memory = Wasmtime::Memory.new(store, min_size: 10, max_size: 100)
  9. Manage Wasmtime::Table elements

    main

    Once a Wasmtime::Table is created, you can interact with its elements using get, set, and grow.

    # Get an element at a specific index (returns nil if out of bounds)
    value = table.get(0)
    
    # Set an element at a specific index
    table.set(0, new_value)
    
    # Grow the table by a delta, providing an initial value for new slots
    table.grow(5, initial_value)
    
    # Get the current size of the table
    current_size = table.size
  10. Initialize a Wasmtime::Engine

    main

    Create a new Wasmtime::Engine instance by passing a configuration hash. The engine is the core execution environment for Wasmtime. You typically only need one engine per program and can reuse it across multiple stores and instances.

    Note on Parallel Compilation and Forking: If your Ruby server uses a pre-forking mechanism (like Unicorn or Puma in certain modes), you must disable parallel_compilation in any engine created before the fork. Compiling Wasm with parallel compilation enabled prior to Process.fork can cause deadlocks. You can safely enable it in engines created after the fork.

    # Disabling parallel compilation for pre-forking environments
    prefork_engine = Wasmtime::Engine.new(parallel_compilation: false)
    wasm_module = Wasmtime::Module.new(prefork_engine, "(module)")
    
    fork do
      # Enable parallel compilation after forking
      engine = Wasmtime::Engine.new(parallel_compilation: true)
      store = Wasmtime::Store.new(engine)
      instance = Wasmtime::Instance.new(store, wasm_module)
    end