嵌入 bindgen 函数

本章节中讨论的 wasm_bindgen 方法已经废弃,我们推荐你查看 wasmedge_bindgen,或者直接传入内存指针;

在这个例子中,我们将演示如何从 Go 应用中调用一些非常简单的 WebAssembly 函数。这些函数都是基于 Rust 语言编写,并且调用参数和返回值都比较复杂。编译工具需要函数声明 #[wasm_bindgen] 宏才能自动生成正确的代码,以便支持从 Go 中传递调用参数给 WebAssembly。

WebAssembly 规范只支持几种开箱即用的简单数据类型,并不支持字符串和数组类型。为了实现在 Go 中向 WebAssembly 传递更丰富的类型,编译器需要将其转换成简单的整数。比如,将一个字符串转换成内存地址和字符串长度。wasm_bindgen 工具嵌入在 rustwasmc 中自动执行转换。

在编写本文档的时候,我们要求 Rust 编译器必须是 1.50 或者以下的版本,这样才能够让 WebAssembly 函数与 WasmEdge 的 Go API 正常工作。当接口类型规范最终确定并完成支持后,我们会尽快迁移到最新的 Rust 版本

  1. #![allow(unused)]
  2. fn main() {
  3. use wasm_bindgen::prelude::*;
  4. use num_integer::lcm;
  5. use sha3::{Digest, Sha3_256, Keccak256};
  6. #[wasm_bindgen]
  7. pub fn say(s: &str) -> String {
  8. let r = String::from("hello ");
  9. return r + s;
  10. }
  11. #[wasm_bindgen]
  12. pub fn obfusticate(s: String) -> String {
  13. (&s).chars().map(|c| {
  14. match c {
  15. 'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
  16. 'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
  17. _ => c
  18. }
  19. }).collect()
  20. }
  21. #[wasm_bindgen]
  22. pub fn lowest_common_multiple(a: i32, b: i32) -> i32 {
  23. let r = lcm(a, b);
  24. return r;
  25. }
  26. #[wasm_bindgen]
  27. pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> {
  28. return Sha3_256::digest(&v).as_slice().to_vec();
  29. }
  30. #[wasm_bindgen]
  31. pub fn keccak_digest(s: &[u8]) -> Vec<u8> {
  32. return Keccak256::digest(s).as_slice().to_vec();
  33. }
  34. }

首先,我们使用 rustwasmc 工具将 Rust 源代码编译成字节码。

$ rustup default 1.50.0 $ cd rust_bindgen_funcs $ rustwasmc build # The output WASM will be pkg/rust_bindgen_funcs_lib_bg.wasm

我们在 Go 程序里面嵌入 WasmEdge 运行 WebAssembly 函数,Go 程序源码如下ExecuteBindgen() 函数调用 #[wasm_bindgen] 转换后的 WebAssembly 函数并传递调用参数。

package main import ( "fmt" "os" "github.com/second-state/WasmEdge-go/wasmedge" ) func main() { // Expected Args[0]: program name (./bindgen_funcs) // Expected Args[1]: wasm or wasm-so file (rust_bindgen_funcs_lib_bg.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 directories ) // Instantiate wasm vm.LoadWasmFile(os.Args[1]) vm.Validate() vm.Instantiate() // Run bindgen functions var res interface{} var err error res, err = vm.ExecuteBindgen("say", wasmedge.Bindgen_return_array, []byte("bindgen funcs test")) if err == nil { fmt.Println("Run bindgen -- say:", string(res.([]byte))) } res, err = vm.ExecuteBindgen("obfusticate", wasmedge.Bindgen_return_array, []byte("A quick brown fox jumps over the lazy dog")) if err == nil { fmt.Println("Run bindgen -- obfusticate:", string(res.([]byte))) } res, err = vm.ExecuteBindgen("lowest_common_multiple", wasmedge.Bindgen_return_i32, int32(123), int32(2)) if err == nil { fmt.Println("Run bindgen -- lowest_common_multiple:", res.(int32)) } res, err = vm.ExecuteBindgen("sha3_digest", wasmedge.Bindgen_return_array, []byte("This is an important message")) if err == nil { fmt.Println("Run bindgen -- sha3_digest:", res.([]byte)) } res, err = vm.ExecuteBindgen("keccak_digest", wasmedge.Bindgen_return_array, []byte("This is an important message")) if err == nil { fmt.Println("Run bindgen -- keccak_digest:", res.([]byte)) } vm.Release() conf.Release() }

接下来,让我们用 WasmEdge Go SDK 来构建 Go 应用。

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0 go build

执行 Go 应用程序,该应用将在 WasmEdge runtime 中执行嵌入的 WebAssembly 函数。

$ ./bindgen_funcs rust_bindgen_funcs/pkg/rust_bindgen_funcs_lib_bg.wasm 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]