golang硬核技术(三)用rust通过wasm调用go,1
前言
Wasm
,即WebAssembly,是一种用来补充JS在运行上不足的“低级”语言——基于二进制编写。其目标之一正是达到在网页上如同运行机器语言一样快速高效。其开发团队分别来自Mozilla、Google、Microsoft、Apple。
Wasm允许用户采用自己熟悉的语言书写(目前支持C/C++/Rust),再在虚拟机引擎在浏览器上运行。它支持沙盒模式,即先用高级语言编写wasm模块,再在JS中以库函数加载。
WebAssembly,我们都知道,是一种新的字节码格式,目前被应用于 web 中,由于其可移植、体积小,安全性的等优点被渐渐广泛认可,但是其主要是运行在浏览器中。
一些天才们,想让 WebAssembly 也可以运行在非浏览器环境中,这就产生了 WASI。
WASI
是一个新的API体系, 由Wasmtime项目设计, 目的是为WASM设计一套引擎无关(engine-indepent),
面向非Web系统(non-Web system-oriented)的API标准.
上面是我粘的,总结起来就是,将各种语言编写成wasm脚本,然后依托一个运行时运行这些脚本 基于这种特点,Wasm在智能合约,云原生,faas等领域非常火热
下面我们将go代码编写成wasm脚本,然后在rust构建的运行时中运行。
golang wasip1
在本月8号,golang发布了最新版本。我知道有小伙伴打不开,我这里放了截图。
意思是说golang已经提供了wasi的preview1支持。并提供go:wasmimport
标签用于引入函数。
至于go:wasmexport
导出标签,目前还不支持。当然小伙伴们也不用着急,我翻了开发者日志,计划在2024年支持。
先写一段测试代码
//go:wasmimport env show
func show(buf unsafe.Pointer, len uint32) (errno uint32)
func main() {
ctn := []byte("hello world")
show(unsafe.Pointer(&ctn[0]), uint32(len(ctn)))
}
先用go:wasmimport
标签引入函数,namespace为env,函数名为show。这个函数的作用是用来显示一段文本。
然后在main函数中调用show函数,显示一个hello world
运行下面的命令编译成wasm脚本
GOOS=wasip1 GOARCH=wasm go build -ldflags "-s -w" -o wasi-go.wasm main.go
wasmer
主流的wasm运行时有**wasmedge、wasmtime、WAVM wasmer
这里我们使用star较高的wasmer
代码如下
use std::mem::MaybeUninit;
use wasmer::{
Extern, Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Memory32, Module, Store,
WasmPtr,
};
use wasmer_wasix::WasiEnv;
#[derive(Debug)]
pub struct WasiFunctionEnv {
memory: MaybeUninit<Memory>,
}
impl Default for WasiFunctionEnv {
fn default() -> Self {
let memory = MaybeUninit::uninit();
Self { memory }
}
}
impl WasiFunctionEnv {
pub fn show(
mut ctx: FunctionEnvMut<'_, WasiFunctionEnv>,
buf: WasmPtr<u8, Memory32>,
len: i32,
) -> i32 {
let (env, store) = ctx.data_and_store_mut();
unsafe {
let view = env.memory.assume_init_ref().view(&store);
let s = buf.read_utf8_string(&view, len as u32).unwrap();
println!("show---> {}", s);
}
1i32
}
}
fn main() {
//加载wasi文件,创建store module
let wasm_bytes = include_bytes!("../wasi_go.wasm");
let mut store = Store::default();
let module = Module::new(&store, wasm_bytes).unwrap();
// let (stdout_tx, mut stdout_rx) = Pipe::channel();
//初始化tokio异步运行时
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let _guard = runtime.enter();
// Run the module.
//初始化wasi函数环境
let mut wasi_env = WasiEnv::builder("hello")
// .args(&["world"])
// .env("KEY", "Value")
// .stdout(Box::new(stdout_tx))
.finalize(&mut store)
.unwrap();
//创建一个外部函数,并注册到环境中
let mut import_object = wasi_env.import_object(&mut store, &module).unwrap();
let env = WasiFunctionEnv::default();
let env = FunctionEnv::new(&mut store, env);
let function_show = Extern::Function(Function::new_typed_with_env(
&mut store,
&env,
WasiFunctionEnv::show,
));
import_object.register_namespace("env", [("show".to_string(), function_show)]);
//创建实例,并对齐内存
let instance = Instance::new(&mut store, &module, &import_object).unwrap();
wasi_env.initialize(&mut store, instance.clone()).unwrap();
let memory = instance.exports.get_memory("memory").unwrap();
env.as_mut(&mut store).memory.write(memory.clone());
//调用go的main函数
let start = instance.exports.get_function("_start").unwrap();
let result = start.call(&mut store, &[]);
println!("result---> {:?}", result);
// let mut buf = String::new();
// let _ = stdout_rx.read_to_string(&mut buf);
wasi_env.cleanup(&mut store, None);
}
注意:
- wasm运行时的内存和rust程序自身的内存是不一样的,所以在读取数据的时候要先对齐内存。
_start
对应go的main
函数。是go唯一导出的函数。所以会退出程序,打印result
可以看到ExitCode::success(0)
- 我这里通过function的方式调起的脚本,也可以通过
WasiEnv.run_with_store
像调用程序一样调起脚本。 Pipe
可以监控脚本的输入输出
运行起来查看效果,正常打印了文本。
尾语
wasm技术前途光明,但目前为止还处在很初级的阶段。
相比js,wasm的性能无疑很好。但和原生的go rust比,相去甚远。我压测了基于tokio-wasi
和hyper-wasi
的http服务和hyper原生的服务,效果相差几十倍。
目前我正尝试在faas上应用wasm技术,效果一般。我在之前一篇博文中有谈到,直接动态库,简单方便高效。
不过wasm也有自身的优势,期待它的未来,