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

I just want to +1 the idea of compiling to a single .wasm file that can be run by any wasi runtime. To me the appeal of wasm is the portability and if you need to deploy the moon CLI to your server in order to run it, it becomes more like any other traditional server-side language (Ruby, php, etc). Likewise with the number of files (.db etc) that the current moon build command creates, it creates an extra burden to attempt to deploy.

Of course I understand it’s early days and these decisions are still being worked out, so I just wanted to chime in with my preferences for 1) minimal number of files, preferrably on .wasm, 2) not needing special tooling to run in production.

1 Like

WASI currently doesn’t play well with wasm-gc (WASI uses memory to pass arguments, while wasm-gc has its own heap), and there is only one WASM runtime (deno) that supports both WASI and wasm-gc. Currently you can call WASI from Moonbit in deno using some cumbersome tricks (manipulate memory directly via inline WASM), and we will investigate into better support in the future.

The _start function is the automatically exported init. But other pub functions in your main package are exported too, and you can call them from your host just like _start.

As @Guest0x0 mentioned in the previous comment, neither WASI nor WASM-gc are accomplished (WASI is entering preview2. WASM-gc is not supported apart from V8 engine, and node is not using the latest version yet, so deno is almost the only choice). I would also like to be able to distribute only one wasm file, as it would be portable and small in size.

With that said, you can already write WASI application with MoonBit (Wasm-GC backend) with the latest features released this week. You can just embed two Wat functions (load and store) to manipulate memory, and you can use the Bytes API to convert to and from MoonBit types.

Deno + a JS module importing https://deno.land/std@0.206.0/wasi/snapshot_preview1.ts (they dropped wasi from standard library after 0.206.0) or npm:@bjorn3/browser_wasi_shim should be enough for the runtime.

The only extra thing to do is to compile first to wat (using --target wasm-gc and --output-wat), change the name of exported memory from moonbit.memory to memory, and compile it to wasm with the support from wasm-tools: wasm-tools parse xxx.wat -o xxx.wasm so as to fulfill the requirement by the WASI standard.

After that, I think what we can do is cross our fingers and wait wasmtime to add gc support. The replacement of runtime should work like a swift if everything goes well.

Ok, if I’m understanding correctly, it sounds like the reason that wasi runtimes are not currently supported is due to the implementation status of wasi and wasm gc and deploying the moon cli is not the desired long-term solution. If that’s the case, then I’m very happy.

1 Like