Skip to content

celestia-island/tairitsu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

109 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tairitsu

Generic WASM Component Runtime Engine


A WebAssembly runtime for running component-model based WASM modules.

Features

  • 🐳 Docker-like Architecture: Image/Container model for managing WASM modules
  • 🔄 Generic Runtime: Does not prescribe any specific WIT interface - users define their own
  • 🎯 Flexible: Builder pattern for custom WIT bindings and host state
  • 🔌 WIT-based: Type-safe communication via WebAssembly Interface Types
  • 🦀 Pure Rust: Built on Wasmtime with the Component Model
  • 📦 Macros: Helper macros to reduce boilerplate
  • 🚀 Dynamic Invocation: Runtime WASM function calls with RON and binary canonical ABI
  • 🔤 RON Support: Rust-friendly serialization for better type compatibility
  • Dual Calling Paths: RON (convenient) + Binary (high-performance) for dynamic invocation

Quick Start

This project uses just as a build system.

Run Examples

Tairitsu provides several examples demonstrating different approaches to WASM integration. Run them using just:

# Run comprehensive tests
just test

For all available commands and their descriptions, see the justfile.

Examples Overview

1. wit-native-macro - Macro-Assisted WIT Interface (Recommended)

The easiest way to define WIT interfaces using procedural macros:

Features:

  • Zero boilerplate - automatic enum generation from WIT-like syntax
  • Compile-time type safety with full IDE support
  • No runtime serialization overhead
  • Simple and intuitive API
wit_interface! {
    interface filesystem {
        read: func(path: String) -> Result<Vec<u8>, String>;
        write: func(path: String, data: Vec<u8>) -> Result<(), String>;
        delete: func(path: String) -> Result<(), String>;
        list: func(directory: String) -> Result<Vec<String>, String>;
    }
}

2. wit-native-simple - Trait-Based WIT Interface

Manual trait implementation for maximum flexibility:

Features:

  • Full control over interface definitions
  • Composable interfaces via traits
  • Zero-cost abstractions
  • Interface extension support

3. wit-dynamic-advanced - Dynamic WASM Component Invocation

Advanced dynamic invocation with actual WASM Components:

Features:

  • 🚀 Runtime guest export calls (RON + Binary)
  • 📥 Host import registration and invocation
  • 🔍 Runtime function discovery
  • 🔤 RON serialization for Rust-friendly types
  • ⚡ Binary canonical ABI for high performance
  • ✅ Full support for basic and complex nested types

4. wit-compile-time - Compile-Time WIT Binding

Static WIT binding with wasmtime bindgen:

Features:

  • Complete compile-time type checking
  • Zero runtime overhead
  • Best performance
  • IDE autocomplete support

5. wit-runtime - Runtime WIT Loading

Dynamic WIT definition loading:

Features:

  • Runtime WIT discovery
  • Interface validation
  • Plugin system support
  • Capability negotiation

See examples/README.md for detailed documentation of each example.

Choosing the Right Approach

Approach Type Safety Performance Flexibility Best For
Macro Full Best Medium Most use cases
Trait Full Best High Complex interfaces
Dynamic RON/WASM Runtime Best (Binary) Highest Plugin systems, hot-reload
Compile-time Full Best Low Fixed interfaces
Runtime Partial Good High Plugin systems

Basic Usage

use tairitsu::{Container, Image, HostState};
use bytes::Bytes;

// 1. Create a WASM image
let wasm_binary = std::fs::read("component.wasm")?;
let image = Image::new(Bytes::from(wasm_binary))?;

// 2. Create container with your WIT bindings
let container = Container::builder(image)?
    .with_guest_initializer(|ctx| {
        // Register your WIT interface (generated by wit-bindgen)
        MyWit::add_to_linker(ctx.linker, |state| &mut state.my_data)?;

        // Instantiate the component
        let instance = MyWit::instantiate(
            ctx.store,
            ctx.component,
            ctx.linker
        )?;

        Ok(GuestInstance::new(instance))
    })?
    .build()?;

// 3. Use the container to call into WASM
let guest = container.guest().downcast_ref::<MyWit>()?;
let result = guest.my_function(ctx.store)?;

Architecture

Tairitsu is a generic WASM runtime engine - it does not prescribe any specific WIT interface:

graph TB
    subgraph Registry["Registry"]
        direction LR
        Image1["Image"]
        Image2["Image"]
        Image3["Image"]
    end

    subgraph Containers["Containers"]
        Container1["Container"]
        Container2["Container"]
    end

    subgraph Container1["Container"]
        direction TB
        Guest["Guest (WASM)"]
        Host["Host"]
        Guest <-->|"User Defined<br/>Communication"| Host
    end

    Registry --> Containers
    Image1 -.->|"Compiled WASM components"| Container1
    Image2 -.->|"Compiled WASM components"| Container1
    Image3 -.->|"Compiled WASM components"| Container2

    style Registry fill:#e1f5ff
    style Containers fill:#fff4e1
    style Container1 fill:#f0e1ff
    style Container2 fill:#f0e1ff
    style Guest fill:#e1ffe1
    style Host fill:#ffe1f0
Loading

Core Concepts

  • Registry: Manages WASM images and running containers (like Docker daemon)
  • Image: A compiled WASM component that can be instantiated (like Docker image)
  • Container: A running instance with user-defined WIT bindings
  • WIT Bindings: Users define their own WIT interfaces using wit-bindgen

Defining Your WIT Interface

  1. Create a .wit file:
interface my-app {
    greet: func(name: string) -> string;
    compute: func(input: string) -> string;
}
  1. Generate bindings with wit-bindgen:
wit-bindgen rust --world my-app
  1. Use the generated bindings with Tairitsu:
use tairitsu::Container;

let container = Container::builder(image)?
    .with_guest_initializer(|ctx| {
        // Use the generated bindings
        MyApp::add_to_linker(ctx.linker, |state| &mut state.data)?;
        let instance = MyApp::instantiate(ctx.store, ctx.component, ctx.linker)?;
        Ok(GuestInstance::new(instance))
    })?
    .build()?;

Helper Macros

Tairitsu provides macros to reduce boilerplate:

impl_wit_interface!

Quickly implement the WitInterface trait:

impl_wit_interface!(MyInterface, "my-interface",
    fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
        dispatcher.register("my-command", Box::new(my_handler));
    }
);

simple_handler!

Create stateless handlers:

let handler = simple_handler!(MyCommand, |cmd| {
    match cmd {
        MyCommand::Foo => Ok(()),
    }
});

stateful_handler!

Create handlers with state:

let handler = stateful_handler!(MyState, MyCommand, |state, cmd| {
    state.counter += 1;
    Ok(state.counter)
});

Advanced Usage

Type-Safe Commands with Zero Serialization

The native approaches (wit-native-*) provide compile-time type safety without runtime serialization overhead:

use tairitsu::{WitCommand, WitCommandHandler, WitCommandDispatcher};

// Define your command types
#[derive(Debug, Clone)]
pub enum MyCommands {
    Process { input: String },
    Query { id: u32 },
}

impl WitCommand for MyCommands {
    type Response = Result<String, String>;

    fn command_name(&self) -> &'static str {
        match self {
            Self::Process { .. } => "process",
            Self::Query { .. } => "query",
        }
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

// Implement handler
struct MyHandler;

impl WitCommandHandler<MyCommands> for MyHandler {
    fn execute(
        &mut self,
        command: &MyCommands,
    ) -> Result<MyCommands::Response, String> {
        match command {
            MyCommands::Process { input } => {
                Ok(Ok(format!("Processed: {}", input)))
            }
            MyCommands::Query { id } => {
                Ok(Ok(format!("Query result for {}", id)))
            }
        }
    }
}

Dynamic RON Invocation

For scenarios requiring both Rust-friendly types and high performance:

use tairitsu::{ron::{typed_ron_tool, RonToolRegistry}, Container, Image};

// RON provides Rust-native type support
#[derive(Deserialize, Serialize)]
struct CalculatorRequest {
    a: i32,
    b: i32,
}

#[derive(Deserialize, Serialize)]
struct CalculatorResponse {
    result: i32,
}

// Create a type-safe tool with RON
let tool = typed_ron_tool("calculator", |input: CalculatorRequest| -> CalculatorResponse {
    CalculatorResponse {
        result: input.a + input.b,
    }
});

// Register and invoke with RON syntax
let mut registry = RonToolRegistry::new();
registry.register("calculator".to_string(), tool);

// RON uses Rust-like syntax
let result = registry.invoke("calculator", "(a: 42, b: 58)")?;

// For WASM Components, use the dynamic invocation API
let mut container = Container::builder(image)?
    .with_guest_initializer(|ctx| {
        // ... setup WIT bindings
        Ok(GuestInstance::new(instance))
    })?
    .build()?;

// RON path - convenient and readable
let result = container.call_guest_raw_desc(
    "add",
    r#"(a: 42, b: 58)"#
)?;

// Binary path - high performance
use wasmtime::component::Val;
let args = vec![Val::S32(42), Val::S32(58)];
let results = container.call_guest_binary("add", &args)?;

RON Type Support:

Type RON Syntax Description
Struct Struct { field: value } Named fields
Tuple (value1, value2) Ordered values
Enum Variant(value) Enum with payload
Option Some(value) / None Optional values
Result Ok(value) / Err(error) Fallible operations
List [item1, item2] Homogeneous arrays
Unit () Empty tuple

Composable Interfaces

Use trait objects to compose multiple WIT interfaces:

use tairitsu::{CompositeWitInterface, WitInterface};

struct FileSystemInterface;
impl WitInterface for FileSystemInterface {
    fn interface_name(&self) -> &'static str {
        "filesystem"
    }

    fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
        // Register handlers
    }
}

struct NetworkInterface;
impl WitInterface for NetworkInterface {
    fn interface_name(&self) -> &'static str {
        "network"
    }

    fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
        // Register handlers
    }
}

// Compose multiple interfaces
let mut composite = CompositeWitInterface::new();
composite.add_interface(Box::new(FileSystemInterface));
composite.add_interface(Box::new(NetworkInterface));

// Register all at once
let mut dispatcher = WitCommandDispatcher::new();
composite.register_all(&mut dispatcher);

Design Philosophy

Tairitsu is designed to be a pure engine - it provides:

  • ✅ Generic WASM container runtime
  • ✅ Image/Registry management
  • ✅ Builder pattern for WIT bindings
  • ✅ Helper macros to reduce boilerplate

It does not prescribe:

  • ❌ Specific WIT interfaces (you define your own)
  • ❌ Command types (you define your own)
  • ❌ Serialization format (use WIT)

This makes Tairitsu suitable for any WASM component-based application.

Architecture Trade-offs

Native vs Dynamic Approaches

Native (Trait-based) - Use when:

  • You control both host and guest code
  • Performance is critical
  • Type safety is a priority
  • Building a closed system

Dynamic RON/WASM - Use when:

  • Plugin systems with hot-reload required
  • Performance-critical dynamic calls needed
  • Rust-friendly serialization preferred
  • Full bidirectional guest-host communication

Choosing Between RON and Binary Paths:

Path Type Safety Performance Use Case
RON Runtime Good Convenient, debugging, cross-language
Binary Runtime Best Hot loops, frequent calls, zero-copy
Static Compile-time Best Known interfaces, maximum type safety

When to Use Each Approach

Macro Approach (wit-native-macro)

  • ✅ Quick development with minimal boilerplate
  • ✅ Best for most applications
  • ✅ Easy to maintain and refactor
  • ❌ Less control than manual trait implementation

Manual Trait Approach (wit-native-simple)

  • ✅ Maximum flexibility and control
  • ✅ Best for complex interface hierarchies
  • ✅ Can optimize for specific use cases
  • ❌ More boilerplate to maintain

Dynamic RON/WASM Approach (wit-dynamic-advanced)

  • ✅ Ideal for plugin systems with hot-reload
  • ✅ Supports actual WASM Component execution
  • ✅ RON for Rust-friendly serialization
  • ✅ Binary path for high-performance calls
  • ✅ Bidirectional guest-host communication
  • ❌ More complex than static approaches
  • ❌ Requires WASM Components (not just tools)

Best Practices

  1. Start with the macro approach - It's the easiest way to get started
  2. Use traits for composition - Combine multiple interfaces using CompositeWitInterface
  3. Prefer native approaches - Use static API for known interfaces
  4. Use RON for dynamic calls - Rust-friendly types with runtime flexibility
  5. Use binary path for hot loops - When performance is critical
  6. Define clear interfaces - Well-designed WIT interfaces make your system more maintainable
  7. Test in isolation - Test handlers independently before integrating with WASM

What's New in 0.3.0

  • 🚀 Dynamic WASM Component Invocation

    • Runtime guest export calls with call_guest_raw_desc() (RON)
    • High-performance binary calls with call_guest_binary()
    • Host import registration and invocation
    • Runtime function discovery
  • 🔤 RON Serialization Support

    • RonBinding and RonToolRegistry for Rust-friendly types
    • Native Rust enum, tuple, option, and result support
    • typed_ron_tool! macro for type-safe tools
  • Dual Calling Paths

    • RON path: Convenient, human-readable serialization
    • Binary path: Zero-copy, canonical ABI performance
  • Complete Type Support

    • All basic types (Bool, Integers, Floats, String, Char)
    • Complex types (List, Tuple, Record, Variant, Result, Option)
    • Full nested type support (e.g., List<List<T>>, Option<Result<T, E>>)
  • 📦 New Example: wit-dynamic-advanced showcasing all new features

See examples/README.md for migration guide and detailed examples.

What is Tairitsu?

Tairitsu (対立) carries a dual meaning:

  1. From Arcaea: Named after the character Tairitsu from the rhythm game Arcaea, representing the "Conflict" side
  2. Opposition & Duality: Reflects the architectural concept of this framework - the inherent duality and opposition between the WASM virtual machine (guest) and the host environment, connected through WIT (WebAssembly Interface Types)

License

See LICENSE for details.

About

Generic WASM Component Runtime Engine

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors