TheSharperDev

Posts about C# and F#

How to Run a WebAssembly Program in F#

I’m currently building a programming language that compiles down to wasm.

If you need to run a wasm program while using F#, here is how you do that.

Install Wasmtime

From my brief searching, Wasmtime was the most supported library for running wasm programs.

There are numerous ways to install it into your application. Here are the Cli commands.

//install latest
dotnet add package Wasmtime

//install specific version
dotnet add package Wasmtime --version 22.0.0

Get Your Wasm/Wat Code

WebAssembly has two different formats:

  • Text format - *.wat files
  • Binary format - *.wasm files

Here is the program that we’ll be running in text format:

(module
  (type $t0 (func (param i32 i32) (result i32)))
  (type $t1 (func (result i32)))
  (func $add (export "add") (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
    (i32.add
      (local.get $p0)
      (local.get $p1)))
  (func $main (export "main") (type $t1) (result i32)
    (call $add
      (i32.const 1)
      (i32.const 2))))

It’s a simple program, has two functions, add and main. main calls add with two numbers and add returns the numbers summed.

There are a variety of tools to convert *.wat <-> *.wasm files, I use wasm-tools.

F# Code

The code here is fairly self explanatory. I import the same code in both binary and text formats, and run them using the wasmtime library.

module RunWasm

open Wasmtime

let private loadAddWat () =
    System.IO.File.ReadAllText "./wasm/add.wat"

let private loadAddWasm () =
    System.IO.File.ReadAllBytes "./wasm/add.wasm"
    
let runAddWat () =
    let text = loadAddWat()

    let engine = new Engine()

    let modd =
        Module.FromText(
            engine,
            "test",
            text
        )

    let linker = new Linker(engine)
    let store = new Store(engine)

    let instance = linker.Instantiate(store, modd)

    let main = instance.GetFunction<int32>("main")

    let result = main.Invoke()

    printfn "%d" result
    
let runAddWasm () =
    let bytes = loadAddWasm()

    let engine = new Engine()

    let modd =
        Module.FromBytes(
            engine,
            "test",
            bytes
        )

    let linker = new Linker(engine)
    let store = new Store(engine)

    let instance = linker.Instantiate(store, modd)

    let main = instance.GetFunction<int32>("main")

    let result = main.Invoke()

    printfn "%d" result

For each source, we:

  • Load program from *.wat or *.wasm file
  • Instantiate the needed classes from Wasmtime library.
  • Declare the function signature of the wasm program
    • instance.GetFunction<int32>("main")
    • This is you telling that you have a function called main, it takes no parameters but returns one int32.
  • Invoke the function
  • Print the result (if any)

Invoking with Other Parameters

The instance.GetFunction can take any number of parameters, you just need to specify them generically.

Here are some other examples:

let func = instance.GetFunction<int32, int32>(funcName)
func.Invoke(param)

let func = instance.GetFunction<int32, int32, int32>(funcName)
func.Invoke(p1, p2)

There are also other useful method on instance class when dealing with webassembly programs.

That’s the Gist

That’s the gist of it.

The code for this post is located in a github repo, that can be checked out and run.