通过 WasmEdge ,用 Rust 函数扩展 Golang 应用

原创
2021/07/06 15:59
阅读数 1.7W

GO 编程语言(Golang)是一种易于使用且安全的编程语言,可编译为高性能的原生应用程序。Golang 是编写软件基础设施和框架的流行选择。

软件框架的一个关键要求是,用户能够使用自己的代码对其进行扩展和定制。但是,在 Golang 应用中,向现有应用程序添加用户定义的函数或扩展并不容易。通常,需要通过组合框架的源代码和用户定义的函数在源代码级别进行集成。虽然可以使用 Golang 创建动态共享模块,但广泛用于边缘计算的系统是基于 ARM 的,缺少对共享模块的支持。此外,源代码集成和动态模块都没有为用户定义的函数提供隔离。这些扩展可能会干扰框架本身。并且集成多方的用户定义函数会不安全。因此,Golang 作为“云原生”的语言,需要更好的扩展机制。

WebAssembly 提供了一种强大、灵活、安全且简单的扩展机制,可以将用户定义的函数嵌入到 Golang 应用程序中。WebAssembly 最初为 Web 浏览器而发明,但越来越多地用于独立和服务端应用程序,WebAssembly 是其字节码应用程序的轻量级软件容器。WebAssembly 是高性能、可移植的,并支持多种编程语言。

在本教程中,我们将讨论如何从 Golang 应用程序运行 WebAssembly 函数。 WebAssembly 函数是用 Rust 编写的。WebAsembly 函数与 Golang 主机应用程序有着很好的隔离,同时函数之间彼此也进行了隔离。

WebAssembly Go

准备工作

显然,我们需要安装 Golang,这里假设你已经安装了。

Golang 版本应该高于 1.15,我们的示例才能工作。

下一步,请安装 WasmEdge 共享库。 WasmEdge 是一个由 CNCF 托管的领先的 WebAssembly Runtime 。我们将使用 WasmEdge 在 Golang 应用程序中嵌入和运行 WebAssembly 程序。

$ wget https://github.com/second-state/WasmEdge-go/releases/download/v0.8.1/install_wasmedge.sh
$ chmod +x ./install_wasmedge.sh
$ sudo ./install_wasmedge.sh /usr/local

最后,由于我们的 demo WebAssembly 函数是用 Rust 编写的,因此还需要安装 Rust 编译器和 rustwasmc 工具链

嵌入一个函数

目前,我们需要 Rust 编译器版本 1.50 或更低版本才能让 WebAssembly 函数与 WasmEdge 的 Golang API 一起使用。一旦 interface type 规范最终确定并得到支持,我们会赶上最新的Rust 编译器版本

示例中,我们将演示如何从 Golang 应用程序调用一些简单的 WebAssembly 函数。这些函数是用 Rust 编写的,需要复杂的调用参数和返回值。编译器工具需要 #[wasm_bindgen]宏来自动生成正确的代码以将调用参数从 Golang 传到 WebAssembly。

WebAssembly 规范仅支持一些开箱即用的简单数据类型。 Wasm 不支持字符串和数组等类型。 为了将 Golang 中的丰富类型传到 WebAssembly,编译器需要将它们转换为简单的整数。 例如,它将字符串转换为整数内存地址和整数长度。 嵌入在 rustwasmc 中的 wasm_bindgen 工具会自动进行这种转换。

use wasm_bindgen::prelude::*;
use num_integer::lcm;
use sha3::{Digest, Sha3_256, Keccak256};

#[wasm_bindgen]
pub fn say(s: &str) -> String {
  let r = String::from("hello ");
  return r + s;
}

#[wasm_bindgen]
pub fn obfusticate(s: String) -> String {
  (&s).chars().map(|c| {
    match c {
      'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
      'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
      _ => c
    }
  }).collect()
}

#[wasm_bindgen]
pub fn lowest_common_multiple(a: i32, b: i32) -> i32 {
  let r = lcm(a, b);
  return r;
}

#[wasm_bindgen]
pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> {
  return Sha3_256::digest(&v).as_slice().to_vec();
}

#[wasm_bindgen]
pub fn keccak_digest(s: &[u8]) -> Vec<u8> {
  return Keccak256::digest(s).as_slice().to_vec();
}

首先,我们使用 rustwasmc 工具将 Rust 源代码编译为 WebAssembly 字节码函数。请使用 Rust 1.50 或者更低版本。

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

Golang 源代码 要运行的在 WasmEdge 中的 WebAssembly 函数示例如下。 ExecuteBindgen() 函数调用 WebAssembly 函数并使用 #[wasm_bindgen] 传入参数。

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.GetImportObject(wasmedge.WASI)
    wasi.InitWasi(
        os.Args[1:],     /// The args
        os.Environ(),    /// The envs
        []string{".:."}, /// The mapping directories
        []string{},      /// The preopens will be empty
    )

    /// 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.Delete()
    conf.Delete()
}

接下来,让我们使用 WasmEdge Golang SDK 构建 Golang 应用程序。

$ go get -u github.com/second-state/WasmEdge-go/wasmedge
$ go build

运行 Golang 应用程序,它将运行嵌入在 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]

嵌入一整个程序

你可以使用最新的 Rust 编译器和 main.rs 创建一个单独的 WasmEdge 应用,然后将其嵌入一个 Golang 应用中。

除了函数, WasmEdge Golang SDK 也可以嵌入独立的 WebAssembly 应用程序,即,将一个带有 main() 函数的 Rust 应用编译为 WebAssembly。

我们的demo Rust 应用程序 从一个文件中读取。注意这里不需要 #{wasm_bindgen] ,因为 WebAssembly 程序的输入和输出数据现在由 STDINSTDOUT 传递。

use std::env;
use std::fs::File;
use std::io::{self, BufRead};

fn main() {
    // Get the argv.
    let args: Vec<String> = env::args().collect();
    if args.len() <= 1 {
        println!("Rust: ERROR - No input file name.");
        return;
    }

    // Open the file.
    println!("Rust: Opening input file \"{}\"...", args[1]);
    let file = match File::open(&args[1]) {
        Err(why) => {
            println!("Rust: ERROR - Open file \"{}\" failed: {}", args[1], why);
            return;
        },
        Ok(file) => file,
    };

    // Read lines.
    let reader = io::BufReader::new(file);
    let mut texts:Vec<String> = Vec::new();
    for line in reader.lines() {
        if let Ok(text) = line {
            texts.push(text);
        }
    }
    println!("Rust: Read input file \"{}\" succeeded.", args[1]);

    // Get stdin to print lines.
    println!("Rust: Please input the line number to print the line of file.");
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        let input = line.unwrap();
        match input.parse::<usize>() {
            Ok(n) => if n > 0 && n <= texts.len() {
                println!("{}", texts[n - 1]);
            } else {
                println!("Rust: ERROR - Line \"{}\" is out of range.", n);
            },
            Err(e) => println!("Rust: ERROR - Input \"{}\" is not an integer: {}", input, e),
        }
    }
    println!("Rust: Process end.");
}




使用 rustwasmc 工具将应用程序编译为 WebAssembly。

$ cd rust_readfile
$ rustwasmc build
# The output file will be pkg/rust_readfile.wasm

Golang 源代码运行在 WasmEdge 中 WebAssembly 函数,如下:

package main

import (
    "os"
    "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
    wasmedge.SetLogErrorLevel()

    var conf = wasmedge.NewConfigure(wasmedge.REFERENCE_TYPES)
    conf.AddConfig(wasmedge.WASI)
    var vm = wasmedge.NewVMWithConfig(conf)
    var wasi = vm.GetImportObject(wasmedge.WASI)
    wasi.InitWasi(
        os.Args[1:],     /// The args
        os.Environ(),    /// The envs
        []string{".:."}, /// The mapping directories
        []string{},      /// The preopens will be empty
    )

    /// Instantiate wasm. _start refers to the main() function
    vm.RunWasmFile(os.Args[1], "_start")

    vm.Delete()
    conf.Delete()
}

接下来,让我们使用 WasmEdge Golang SDK 构建 Golang 应用程序。

$ go get -u github.com/second-state/WasmEdge-go
$ go build

运行 Golang 应用。

$ ./read_file rust_readfile/pkg/rust_readfile.wasm file.txt
Rust: Opening input file "file.txt"...
Rust: Read input file "file.txt" succeeded.
Rust: Please input the line number to print the line of file.
# Input "5" and press Enter.
5
# The output will be the 5th line of `file.txt`:
abcDEF___!@#$%^
# To terminate the program, send the EOF (Ctrl + D).
^D
# The output will print the terminate message:
Rust: Process end.

接下来

本文中,我们展示了在 Golang 应用程序中嵌入 WebAssembly 函数的两种方法:嵌入一个 WebAssembly 函数以及嵌入一个完整的程序。 更多示例可以参考 WasmEdge-go-examples GitHub repo

下一篇文章,我们将研究将 AI 推理(图像识别)函数嵌入到基于 Golang 的实时流数据处理框架的完整示例。这在智能工厂和汽车中有实际应用。

插播

7月10日,WasmEdge 将亮相 GTOC 开源技术峰会,带来 《WebAssembly 在云原生时代的应用》的主题演讲,欢迎大家来现场交流!

GOTC 全球开源技术峰会是由开放原子开源基金会与 Linux 基金会联合开源中国发起的,面向全球开发者的一场盛大开源技术盛宴。大会将携头部开源公司和顶级开源项目一起亮相,覆盖云原生、大数据、人工智能、物联网、区块链、DevOps、开源治理等多个技术领域,在为期 2 天的时间里,为开发者带来全球最新、最纯粹的开源技术,同时传播开源文化和理念,推动开源生态的建设和发展。 报名直戳:https://gotc.oschina.net/

展开阅读全文
加载中

作者的其它热门文章

打赏
0
14 收藏
分享
打赏
12 评论
14 收藏
0
分享
返回顶部
顶部
返回顶部
顶部