# 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 的开发者来说,会更加有吸引力。