Embed a Wasm function

The WasmEdge Go SDK allows WebAssembly functions to be embedded into a Go host app. You can use the Go SDK API to pass call parameters to the embedded WebAssembly functions, and then capture the return values. However, the WebAssembly spec only supports a few simple data types out of the box. It does not support types such as string and array. In order to pass rich types in Go to WebAssembly, we could hand-code memory pointers (see here), or use an automated tool to manage the data exchange.

The wasmedge-bindgen project provides Rust macros for functions to accept and return complex data types, and then for Go functions to call such Rust functions running in WasmEdge. The full source code for the demo in this chapter is available here.

Rust function compiled into WebAssembly

In the Rust project, all you need is to annotate your functions with a [wasmedge_bindgen] macro. Those annotated functions will be automatically instrumented by the Rust compiler and turned into WebAssembly functions that can be called from the wasmedge-bindgen GO SDK. In the example below, we have several Rust functions that take complex call parameters and return complex values.

  1. #![allow(unused)]
  2. fn main() {
  3. use wasmedge_bindgen::*;
  4. use wasmedge_bindgen_macro::*;
  5. use num_integer::lcm;
  6. use sha3::{Digest, Sha3_256, Keccak256};
  7. use serde::{Serialize, Deserialize};
  8. #[derive(Serialize, Deserialize, Debug)]
  9. struct Point {
  10. x: f32,
  11. y: f32
  12. }
  13. #[derive(Serialize, Deserialize, Debug)]
  14. struct Line {
  15. points: Vec<Point>,
  16. valid: bool,
  17. length: f32,
  18. desc: String
  19. }
  20. #[wasmedge_bindgen]
  21. pub fn create_line(p1: String, p2: String, desc: String) -> Result<Vec<u8>, String> {
  22. let point1: Point = serde_json::from_str(p1.as_str()).unwrap();
  23. let point2: Point = serde_json::from_str(p2.as_str()).unwrap();
  24. let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();
  25. let valid = if length == 0.0 { false } else { true };
  26. let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: desc };
  27. return Ok(serde_json::to_vec(&line).unwrap());
  28. }
  29. #[wasmedge_bindgen]
  30. pub fn say(s: String) -> Result<Vec<u8>, String> {
  31. let r = String::from("hello ");
  32. return Ok((r + s.as_str()).as_bytes().to_vec());
  33. }
  34. #[wasmedge_bindgen]
  35. pub fn obfusticate(s: String) -> Result<Vec<u8>, String> {
  36. let r: String = (&s).chars().map(|c| {
  37. match c {
  38. 'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
  39. 'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
  40. _ => c
  41. }
  42. }).collect();
  43. Ok(r.as_bytes().to_vec())
  44. }
  45. #[wasmedge_bindgen]
  46. pub fn lowest_common_multiple(a: i32, b: i32) -> Result<Vec<u8>, String> {
  47. let r = lcm(a, b);
  48. return Ok(r.to_string().as_bytes().to_vec());
  49. }
  50. #[wasmedge_bindgen]
  51. pub fn sha3_digest(v: Vec<u8>) -> Result<Vec<u8>, String> {
  52. return Ok(Sha3_256::digest(&v).as_slice().to_vec());
  53. }
  54. #[wasmedge_bindgen]
  55. pub fn keccak_digest(s: Vec<u8>) -> Result<Vec<u8>, String> {
  56. return Ok(Keccak256::digest(&s).as_slice().to_vec());
  57. }
  58. }

You can build the WebAssembly bytecode file using standard Cargo commands.

cd rust_bindgen_funcs cargo build --target wasm32-wasi --release # The output WASM will be target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm. cp target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm ../ cd ../

Go host application

In the Go host application, you can create and set up the WasmEdge VM using the WasmEdge Go SDK. However, instead of calling vm.Instantiate(), you should now call bindgen.Instantiate(vm) to instantiate the VM and return a bindgen object.

func main() { // Expected Args[0]: program name (./bindgen_funcs) // Expected Args[1]: wasm file (rust_bindgen_funcs_lib.wasm)) wasmedge.SetLogErrorLevel() var conf = wasmedge.NewConfigure(wasmedge.WASI) var vm = wasmedge.NewVMWithConfig(conf) var wasi = vm.GetImportModule(wasmedge.WASI) wasi.InitWasi( os.Args[1:], // The args os.Environ(), // The envs []string{".:."}, // The mapping preopens ) vm.LoadWasmFile(os.Args[1]) vm.Validate() // Instantiate the bindgen and vm bg := bindgen.Instantiate(vm)

Next, you can call any [wasmedge_bindgen] annotated functions in the VM via the bindgen object.

// create_line: string, string, string -> string (inputs are JSON stringified) res, err := bg.Execute("create_line", "{\"x\":2.5,\"y\":7.8}", "{\"x\":2.5,\"y\":5.8}", "A thin red line") if err == nil { fmt.Println("Run bindgen -- create_line:", string(res)) } else { fmt.Println("Run bindgen -- create_line FAILED", err) } // say: string -> string res, err = bg.Execute("say", "bindgen funcs test") if err == nil { fmt.Println("Run bindgen -- say:", string(res)) } else { fmt.Println("Run bindgen -- say FAILED") } // obfusticate: string -> string res, err = bg.Execute("obfusticate", "A quick brown fox jumps over the lazy dog") if err == nil { fmt.Println("Run bindgen -- obfusticate:", string(res)) } else { fmt.Println("Run bindgen -- obfusticate FAILED") } // lowest_common_multiple: i32, i32 -> i32 res, err = bg.Execute("lowest_common_multiple", int32(123), int32(2)) if err == nil { num, _ := strconv.ParseInt(string(res), 10, 32) fmt.Println("Run bindgen -- lowest_common_multiple:", num) } else { fmt.Println("Run bindgen -- lowest_common_multiple FAILED") } // sha3_digest: array -> array res, err = bg.Execute("sha3_digest", []byte("This is an important message")) if err == nil { fmt.Println("Run bindgen -- sha3_digest:", res) } else { fmt.Println("Run bindgen -- sha3_digest FAILED") } // keccak_digest: array -> array res, err = bg.Execute("keccak_digest", []byte("This is an important message")) if err == nil { fmt.Println("Run bindgen -- keccak_digest:", res) } else { fmt.Println("Run bindgen -- keccak_digest FAILED") } bg.Release() vm.Release() conf.Release() }

Finally, you can build and run the Go host application.

go build ./bindgen_funcs rust_bindgen_funcs_lib.wasm

The standard output of this example will be the following.

Run bindgen -- create_line: {"points":[{"x":1.5,"y":3.8},{"x":2.5,"y":5.8}],"valid":true,"length":2.2360682,"desc":"A thin red line"} Run bindgen -- say: hello bindgen funcs test Run bindgen -- obfusticate: N dhvpx oebja sbk whzcf bire gur ynml qbt Run bindgen -- lowest_common_multiple: 246 Run bindgen -- sha3_digest: [87 27 231 209 189 105 251 49 159 10 211 250 15 159 154 181 43 218 26 141 56 199 25 45 60 10 20 163 54 211 195 203] Run bindgen -- keccak_digest: [126 194 241 200 151 116 227 33 216 99 159 22 107 3 177 169 216 191 114 156 174 193 32 159 246 228 245 133 52 75 55 27]