WebAssembly / component-model

Repository for design and specification of the Component Model
Other
897 stars 75 forks source link

Question: how to store a global variable? #364

Closed chaosprint closed 1 month ago

chaosprint commented 1 month ago

First of all, thanks to the contributors of this project, apologies If this question has been answered somewhere else.

But when I look at the documents and examples, a natural confusion for me is that I don't know how to store a global state, or the state in the interface.

For example, adder, if I want to store the current accumulation, which is initially 0, and update the accumulation every time adder is called, how can I do it?

It would be nice to have common examples like storing student names and ages.

chaosprint commented 1 month ago

my latest try:

// world.wit

package component:wasm-cm3;

/// An example world for the component to target.
world example {
    export addx: func(input: f32);
}
#[allow(warnings)]
mod bindings;

use bindings::Guest;

use lazy_static::lazy_static;
use parking_lot::Mutex;

// def a global accumulator
lazy_static! {
    static ref ACCUMULATOR: Mutex<f32> = Mutex::new(0.0);
}

struct Component;

impl Guest for Component {
    /// Say hello!
    fn addx(x: f32) {
        let mut acc = ACCUMULATOR.lock();
        *acc += x;
        println!("Accumulator: {}", *acc);
    }
}

bindings::export!(Component with_types_in bindings);
// main.rs

use wasmtime::{
    component::{Component, Linker, Val},
    Config, Engine, Store,
};
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};

fn main() -> anyhow::Result<()> {
    let mut config = Config::default();
    config.wasm_component_model(true);
    let engine = Engine::new(&config)?;
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker_sync(&mut linker).expect("Failed to link command world");

    let wasi_view = ServerWasiView::new();
    let mut store = Store::new(&engine, wasi_view);

    let bytes = std::fs::read("../wasm-cm3/target/wasm32-wasi/release/wasm_cm3.wasm")?;
    let component = Component::new(&engine, bytes)?;
    let instance = linker.instantiate(&mut store, &component)?;

    let func = instance
        .get_func(&mut store, "addx")
        .expect("greet export not found");
    func.call(&mut store, &[Val::Float32(20.0)], &mut [])?;

    let func = instance
        .get_func(&mut store, "addx")
        .expect("greet export not found");
    func.call(&mut store, &[Val::Float32(22.0)], &mut [])?;

    Ok(())
}

struct ServerWasiView {
    table: ResourceTable,
    ctx: WasiCtx,
}

impl ServerWasiView {
    fn new() -> Self {
        let table = ResourceTable::new();
        let ctx = WasiCtxBuilder::new().inherit_stdio().build();

        Self { table, ctx }
    }
}

impl WasiView for ServerWasiView {
    fn table(&mut self) -> &mut ResourceTable {
        &mut self.table
    }

    fn ctx(&mut self) -> &mut WasiCtx {
        &mut self.ctx
    }
}

but this will yield when the second func is called

Accumulator: 20
Error: wasm trap: cannot enter component instance
alexcrichton commented 1 month ago

One perhaps important point is that this repository is intended to document the component model standard and doesn't necessarily house examples of how to use components with various runtimes/languages. That might be best to ask on the Bytecode Alliance Zulip chat for example.

Otherwise though you're basically along the right lines. Storing global state is done in Rust for components the same way it's done on native: in a static. Your example almost works but you'll want to be sure to consult the documentation for Func::call if you're eschewing bindgen!-generated bindings. Notably you need to call Func::post_return

chaosprint commented 1 month ago

One perhaps important point is that this repository is intended to document the component model standard and doesn't necessarily house examples of how to use components with various runtimes/languages. That might be best to ask on the Bytecode Alliance Zulip chat for example.

Otherwise though you're basically along the right lines. Storing global state is done in Rust for components the same way it's done on native: in a static. Your example almost works but you'll want to be sure to consult the documentation for Func::call if you're eschewing bindgen!-generated bindings. Notably you need to call Func::post_return

thank you. adding post_return does solve the problem. I will ask in Zulip next time, although I feel that asking here can be a better reference if some one has the same error and use search engine such as this one: https://github.com/bytecodealliance/wasmtime/issues/8670