I think it’s likely that in the future Moonbit will support the WASI standard, but today as far as I know the only way is by passing functions. Here are a few examples I’ve used with wazero and wasmtime that are similar to some of the official examples using NodeJS:
package main
import (
"context"
_ "embed"
"fmt"
"log/slog"
"math"
"os"
"github.com/tetratelabs/wazero"
)
//go:embed target/build/main/main.wasm
var moonWasm []byte
func print_i32(i int32) {
fmt.Print(i)
}
func print_f64(f float64) {
fmt.Print(f)
}
func print_char(c int32) {
fmt.Print(string(uint8(c)))
}
func get_pi() float64 {
return math.Pi
}
func sqrt(f float64) float64 {
return math.Sqrt(f)
}
func main() {
ctx := context.Background()
r := wazero.NewRuntime(ctx)
defer r.Close(ctx)
_, err := r.NewHostModuleBuilder("spectest").NewFunctionBuilder().WithFunc(print_i32).Export("print_i32").NewFunctionBuilder().WithFunc(print_f64).Export("print_f64").NewFunctionBuilder().WithFunc(print_char).Export("print_char").Instantiate(ctx)
if err != nil {
slog.Error("Failed to build spectest functions", "err", err)
os.Exit(1)
}
_, err = r.NewHostModuleBuilder("math").NewFunctionBuilder().WithFunc(sqrt).Export("sqrt").NewFunctionBuilder().WithFunc(get_pi).Export("get_pi").Instantiate(ctx)
if err != nil {
slog.Error("Failed to build math functions", "err", err)
os.Exit(1)
}
_, err = r.Instantiate(ctx, moonWasm)
if err != nil {
slog.Error("Failed to instantiate wasm", "err", err)
os.Exit(1)
}
}
use std::f64::consts::PI;
use anyhow::Result;
use wasmtime::{Engine, Linker, Module, Store};
fn main() -> Result<()> {
let engine = Engine::default();
let module = Module::from_file(&engine, "/path/to/main.wat")?;
let mut linker = Linker::new(&engine);
linker.func_wrap("spectest", "print_i32", |i: i32| print!("{}", i))?;
linker.func_wrap("spectest", "print_f64", |f: f64| print!("{}", f))?;
linker.func_wrap("spectest", "print_char", |c: i32| print!("{}", c as u8 as char))?;
linker.func_wrap("math", "get_pi", || PI)?;
linker.func_wrap("math", "sqrt", |x: f64| x.sqrt())?;
let mut store = Store::new(&engine, 4);
linker.module(&mut store, "", &module)?;
linker
.get_default(&mut store, "")?
.typed::<(), ()>(&store)?
.call(&mut store, ())?;
Ok(())
}
The above only run the _start
function which is automatically exported by default by the init
functions as I understand it. To do more sophisticated things, you may also need to have I/O handlers that would call into different functions in your Moonbit code (i.e. instead of only passing functions in, you may need to export more functions for use in the “runtime” code).