Moonbit on the server

Has anybody gotten moonbit code to run on wasmtime, wasmedge, or wazero? I’d like to use moonbit on the server and these are some of the popular options for that.

Also, these types of wasm engines don’t really need a specific FFI for moonbit (calling from moonbit to host), they all have their own mechanisms for this, usually involving linking.

thanks
Ian

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).

1 Like