# Link or Instantiation 实例化一个 wasm。 ## wasmtime 可以通过 `Instance::new` 直接实例化。 不过更推荐的是使用 Linker。 ### Linker 这个 Linker 类似于一个动态连接器。 * 它可以注册 host 函数 * 实例化 WASM-Module(分配内存什么的) * **最神奇的一点,可以融合多个 WASM-Module** 针对第三点,在代码中有这样一个 example ``` let mut linker = Linker::new(&engine); // Instantiate a small instance and inform the linker that the name of // this instance is `instance1`. This defines the `instance1::run` name // for our next module to use. let wat = r#"(module (func (export "run") ))"#; let module = Module::new(&engine, wat)?; linker.module(&mut store, "instance1", &module)?; let wat = r#" (module (import "instance1" "run" (func $instance1_run)) (func (export "run") call $instance1_run ) ) "#; let module = Module::new(&engine, wat)?; let instance = linker.instantiate(&mut store, &module)?; ``` 这个功能虽然现在用处不大,因为每个 wasm 都会有自己的内存分配器,它们在同一个 Linker 里面,实例化之后会操作同一块内存,不知道会不会互相影响(没有试验过)。 **但是!这个功能有可能会发展成 wasm 的动态链接的标准**,像在上面的示例中,第一个 wat 就是一个动态链接库了,提供了功能。而第二个 wat,只是引入来使用而已。 **【重要功能 — 动态链接】希望 wasmedge 跟上** ## wasmedge (c-api) * WasmEdge_VMLoadWasmFromFile * WasmEdge_VMLoadWasmFromBuffer * WasmEdge_VMLoadWasmFromASTModule WasmEdge_VMContext 相于 wasmtime 的 Instance ## wasmedge (rust-api) * Vm::load_wasm_from_ast_module Vm 相当于 wasmtime 的 Instance。 以目前的api来看,rust 的 api 只能从文件加载。但是从其他地方读取 wasm 的二进制数据是很常见的需求。 **【重要功能 — Rust Vm load from buffer】** # Import & Export 向 wasm 中注入 host_function 、table、global、memory。 获取 wasm 导出的 function 、table、global、memory。 ## wasmtime ### Import ``` let mut linker = Linker::new(&engine); let ty = GlobalType::new(ValType::I32, Mutability::Const); let global = Global::new(&mut store, ty, Val::I32(0x1234))?; linker.define("host", "offset", global)?; let wat = r#" (module (import "host" "offset" (global i32)) (memory 1) (data (global.get 0) "foo") ) "#; let module = Module::new(&engine, wat)?; linker.instantiate(&mut store, &module)?; ``` 通过这个 Linker::define 可以向 Linker 中注册 host_function、table、global、memory。 这个功能也很有必要。 **【重要功能 — set Import】这个 c-api 里面可能有,但是 rust-api 里面没有** ### Export ``` Instance::get_export( &self, mut store: impl AsContextMut, name: &str )// 这个是总的 api,可以代替下文的 api Instance::get_global( &self, mut store: impl AsContextMut, name: &str ) Instance::get_memory( &self, mut store: impl AsContextMut, name: &str ) Instance::get_table( &self, mut store: impl AsContextMut, name: &str ) Instance::get_func( &self, mut store: impl AsContextMut, name: &str )//这个 api 用 run 来代替其实也没有什么问题。 ``` 可以通过这个 api 从之前提到的 Instance 里面获取一个 Export 的对象。 Linker里面也有类似的 api。 **对应到 WasmEdge 里面,应该是一个 Vm::get 的 api** **【重要功能 — get Export】这个 c-api 里面可能有 但是 rust-api 里面没有** ## wasmedge(c-api) c/c++ 水平有限,没有看明白,可能有相应的api ## wasmedge(rust-api) 没有类似的 api,相对来讲是缺失的。 # Async Call **【重要功能 — Async or Generator】** **这个功能是 wasmtime 的杀手级功能** [(demo)](https://github.com/bytecodealliance/wasmtime/blob/main/examples/tokio/main.rs)。wasmedge 目前没有。 它可以把一个 async 的 rust 函数 import 进 wasm 中。然后在调用 wasm 中的函数时(例如 main)可以借助 rust 的 future 功能,与 tokio 实现异步 io,极大的提升 wasm 的 io 效率,也大大减小了 host 环境的负担。甚至可以做到,如果一个语言不支持异步(例如 java),当它可以编译成wasm的时候,可以想 go 一样,在 io 处原生的支持异步 io,对于不支持协程的语言来说,在 wasm 中运行可能会比 native 的高并发性能更好。 但是 wasmtime 实现的是 async。它依赖于 rust 本身的 future 机制,也就是说需要 rust 编译器的支持(我目前看到的样子)。从通用性来说,目前 c 语言他们并没有支持。 但是相对应的,如果采用 Generator (yield & resume)的模式,可以很好的兼容多种语言,虽然对于使用 api 进行二次开发的 host 的开发者来说,会复杂一点,但是我相信敢于挑战这个难题的开发者,这点麻烦不算什么。 **所以我建议 wasmedge 采用 Generator 的形式。** 具体API需求分两部分。 * host_function 在 host_function 中可以调用一个 Yield 函数。调用这个函数之后 会保存 VM 的状态(包括 PC寄存器 )。然后弹出到 Host 中。 * Call Wasm Function 在执行 Wasm Function 的调用栈中,如果收到了 Host Function的 Yield 弹出。会退出调用,之后可以通过一个 Resume 函数恢复继续执行。 example: ``` WasmEdge_Result HostFn(void *Data, WasmEdge_MemoryInstanceContext *MemCxt, const WasmEdge_Value *In, WasmEdge_Value *Out) { int32_t Val1 = WasmEdge_ValueGetI32(In[0]); printf("Yield %d",Val1 + 1); int32_t* val = WasmEdge_Yield(In) Out[0] = WasmEdge_ValueGenI32(*val); return WasmEdge_Result_Success; } int main(int Argc, const char* Argv[]) { // 初始化,不重要 WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate(); WasmEdge_ConfigureAddHostRegistration(ConfCxt,WasmEdge_HostRegistration_Wasi); WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL); WasmEdge_ImportObjectContext *WasiObject = WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_Wasi); WasmEdge_ImportObjectInitWASI(WasiObject,Argv+1,Argc-1,NULL,0,&dirs,1,NULL,0); // 注册函数 不重要 WasmEdge_String ExportName = WasmEdge_StringCreateByCString("extern"); WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(ExportName, NULL); enum WasmEdge_ValType ParamList[1] = { WasmEdge_ValType_I32 }; enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 }; WasmEdge_FunctionTypeContext *HostFType = WasmEdge_FunctionTypeCreate(ParamList, 1, ReturnList, 1); WasmEdge_HostFunctionContext *HostFunc = WasmEdge_HostFunctionCreate(HostFType, HostFn, 0); WasmEdge_FunctionTypeDelete(HostFType); WasmEdge_String HostFuncName = WasmEdge_StringCreateByCString("host_inc"); WasmEdge_ImportObjectAddHostFunction(ImpObj, HostFuncName, HostFunc); WasmEdge_StringDelete(HostFuncName); WasmEdge_VMRegisterModuleFromImport(VMCxt, ImpObj); // WasmEdge_VMInitFromFile 函数不存在,可以用已有的函数替代 // 这一步骤的目的是要实例化一个 WASM /** (module (import "extern" "host_inc" (func $host_inc (param i32) (result i32))) (func $demo_fn (result i32) i32.const 1 (call $host_inc)) (export "demo_fn" (func $demo_fn)) ) **/ WasmEdge_VMInitFromFile(VMCxt, Argv[1]); WasmEdge_Value Params[0]; WasmEdge_Value Returns[1]; WasmEdge_String FuncName = WasmEdge_StringCreateByCString("demo_fn"); // WasmEdge_VMRun 函数不存在,这一步的目的是运行 demo_fn函数 // 但是 WasmEdge_VMRun 也可以用已有函数代替 或者新增此 API // 注意!!! init VMCxt 和 Run 要分离开。 WasmEdge_Result Res = WasmEdge_VMRun(VMCxt,FuncName,Params,Returns); // 新增 API: WasmEdge_ResultPending(VMCxt,Res) // 注意!!! VM 在 Pending 状态下不可以再运行其他函数 // 在这个例子中,wasm 函数"demo_fn" 执行到 HostFn 时 // 会输出 Yield 1, 然后弹出来,并且VM和Res的状态是Pending void* YieldValue = WasmEdge_ResultPending(VMCxt,Res); if(YieldValue){ // todo something // 因为在 HostFn 里面 Yield 的是一个 WasmEdge_Value*; int32_t v = WasmEdge_ValueGetI32((WasmEdge_Value*)YieldValue); // 恢复执行, 会从 HostFn 的 Yield 返回处开始执行,返回值是 Resume 的入参。 Res = WasmEdge_VMResume(VMCxt,&v); } // ...... if(WasmEdge_ResultOk(Res)){ // Ok 意味着函数执行结束了 // Get result: 2 // 因为里面内置的 demo 入参是 1 printf("Get result: %d\n",WasmEdge_ValueGetI32(Returns[0])); } /* Resources deallocations. */ WasmEdge_VMDelete(VMCxt); WasmEdge_ConfigureDelete(ConfCxt); WasmEdge_StringDelete(FuncName); return 0; } ``` **总结-新增API** ``` 入参可以有变化 必须API void* WasmEdge_ResultPending(WasmEdge_VMContext*,WasmEdge_Result) void* WasmEdge_Yield(void* Data) WasmEdge_Result WasmEdge_VMResume(void* Data) // WasmEdge_VMResume 会传递数据给 WasmEdge_Yield // WasmEdge_Yield会传递数据给 WasmEdge_ResultPending 可选API void WasmEdge_VMInitFromFile(WasmEdge_VMContext*,char*) WasmEdge_Result WasmEdge_VMRun(WasmEdge_VMContext*,WasmEdge_String,WasmEdge_Value[],WasmEdge_Value[]) ``` rust-example: ``` fn main()-> Result<(), Box<dyn std::error::Error>>{ let module_path = std::path::PathBuf::from("demo.wasm"); let config = wasmedge_sdk::Config::default(); let module = wasmedge_sdk::Module::new(&config, &module_path)?; let vm = wasmedge_sdk::Vm::load(&module)? .with_config(&config)? .create()?; if let VmRunResult::Pending(yield_v:i32)=vm::run::<i32>("demo_fn",&[]){ if let VmRunResult::Ok(res:i32) = vm::resume(yield_v+1){ println!("demo_fn()={}",res) } } } ``` 这个机制在 rust 中可能会配合 tokio,所以一定要注意满足线程间移动的需求(Sync+Send 那一套)。 但是我这样设计会有一个问题。无法多线程同时调用同一个 VM 的函数。本来这样操作同一块内存是很容易出现问题。 假如是多线程io的话,就完全可以用异步 io+ yield 没有任何问题。 但是多线程计算密集的话,目前还没有想好,有两种方案 * 内存共享,多个 VMCxt 共享一个 memory,要求wasm开发者做好内存琐。 * actor模型,内存隔离。对 wasm 开发者来说,可能一时间难以理解。 # interrupt ## wasmtime https://github.com/bytecodealliance/wasmtime/blob/main/examples/interrupt.rs ## wasmedge wasmedge暂时没有这个功能,这个功能还是比较实用的,因为在 aot 之后,一个函数执行的时候,例如死循环的时候,是不会处理 interrupt 信号的。就无法按时间中断一个执行中的 wasm 。有了这个功能,对于 runtime 来说 比较好做资源的控制。 如果可以配合 async ,在中断之后,还能恢复回去的话,就可以让 runtime 做到时间片的分配,这样对于 runtime 的开发者来说,会更加有吸引力。