An example of running wasmtime as a host, with callbacks.

This commit is contained in:
Elf M. Sternberg 2023-04-05 13:31:56 -07:00
commit d0489776ee
6 changed files with 1823 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/target

1698
rust-wasi-markdown/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
[package]
name = "rust-wasi-markdown"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.70"
pulldown-cmark = "0.9.2"
structopt = "0.3.26"
wasmtime = "7.0.0"

View File

@ -0,0 +1,11 @@
# Hello, World!
This is my first WASI program. Kinda sucks, huh?
> Don't say that!
But why not?
> Because this is cool.
Who says?

View File

@ -0,0 +1,5 @@
(module
(func $hello (import "" "hello"))
(func (export "run") (call $hello)))

View File

@ -0,0 +1,95 @@
use anyhow::Result;
use wasmtime::*;
struct MyState {
name: String,
count: usize,
}
const HELLO: &[u8; 78] =
b"(module (func $hello (import \"\" \"hello\")) (func (export \"run\") (call $hello)))";
fn main() -> Result<()> {
println!("Compiling module...");
// The Engine is the thing that manages and runs WASM instances.
let engine = Engine::default();
// The Module loads, compiles, and creates a new WASM module, ready to be
// run.
let module = Module::new(&engine, HELLO)?;
println!("Initializing...");
// The Store is where a WASM instance runs and includes its memory pages. It
// is the mechanism through which the host and guest programs interact.
let mut store = Store::new(
&engine,
MyState {
name: "hello world".to_string(),
count: 0,
},
);
println!("Creating callback...");
// A Func is a function defined by the host program and that can be called
// from a WASM module. Here, we're creating a host function that accesses
// data inside the store, and increments a mutable value within that data.
// Due to the nature of the store, all such functions end up as Fn: Send +
// Sync + 'static, with no 'FnMut' or 'FnOnce' defined, but the `Caller`
// type there still grants access to the inner workings. See the
// documentation for more.
let hello_func = Func::wrap(&mut store, |mut caller: Caller<'_, MyState>| {
println!("Calling back...");
println!("> {}, {}", caller.data().name, caller.data().count);
caller.data_mut().count += 1;
});
// An Instance is a running instance of the WASM module. By "running" I mean
// that it is now somewhere in memory and its functions can be called by the
// host. Note that the imports put *into* the instance are in order, not
// named, and must be extracted by a similarly ordered collection of imports
// on the other end.
println!("Instantiating module...");
let imports = [hello_func.into()];
let instance = Instance::new(&mut store, &module, &imports)?;
// Inside the module, we have a named export, "run". Here, we access it.
println!("Extracting import...");
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
// And now we run it a couple of times. `run` calls `hello_func` defined
// above, and with each run the data persistent in the `store` object gets
// updated, so we can see the count rising:
println!("Running export...");
run.call(&mut store, ())?;
run.call(&mut store, ())?;
run.call(&mut store, ())?;
run.call(&mut store, ())?;
run.call(&mut store, ())?;
println!("Done!");
/*
The output should look like this:
Compiling module...
Initializing...
Creating callback...
Instantiating module...
Extracting import...
Running export...
Calling back...
> hello world, 0
Calling back...
> hello world, 1
Calling back...
> hello world, 2
Calling back...
> hello world, 3
Calling back...
> hello world, 4
Done!
*/
Ok(())
}