WasmEdge Module 介紹
簡介
簡單介紹 WasmEdge 裡面具名跟匿名模組的差別。
簡單例子
假設我們有兩個簡單的 Wasm 檔案:
provider.wat: 提供一個add函式consumer.wat: 導入 (Import)provider的add函式並有一個run函式執行
// provider.wat
(module
(func $add (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add)
(export "add" (func $add))
)
// consumer.wat
(module
(import "provider" "add" (func $add (param i32 i32) (result i32)))
(func (export "run") (param i32 i32) (result i32)
local.get 0
local.get 1
call $add)
)我們可以使用 wat2wasm 工具將它們編譯成 .wasm 檔案,使用 wat2wasm provider.wat -o provider.wasm。
接下來,我們使用 WasmEdge 的 C API 來撰寫一段執行邏輯:
#include <iostream>
#include <wasmedge/wasmedge.h>
int main() {
// 1. 建立環境配置與 VM 容器
WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
std::cout << "[Step 1] Registering 'provider' module..." << std::endl;
// 這裡會將 provider.wasm 載入、實例化並註冊到 Store 裡,名稱叫做 "provider"
WasmEdge_String ProviderName = WasmEdge_StringCreateByCString("provider"); // Wasm 自己的字串類型
WasmEdge_Result Res1 =
WasmEdge_VMRegisterModuleFromFile(VMCxt, ProviderName, "provider.wasm");
if (!WasmEdge_ResultOK(Res1)) {
std::cerr << "Registration failed: " << WasmEdge_ResultGetMessage(Res1)
<< std::endl;
return 1;
}
std::cout << "[Step 2] Running 'consumer.wasm'..." << std::endl;
// 執行時,它會自動去 Store 找名為 "provider" 的模組
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("run");
WasmEdge_Value Params[2] = {WasmEdge_ValueGenI32(10),
WasmEdge_ValueGenI32(20)};
WasmEdge_Value Returns[1];
WasmEdge_Result Res2 = WasmEdge_VMRunWasmFromFile(
VMCxt, "consumer.wasm", FuncName, Params, 2, Returns, 1);
if (WasmEdge_ResultOK(Res2)) {
std::cout << ">>> Execution Result: " << WasmEdge_ValueGetI32(Returns[0])
<< " <<<" << std::endl;
} else {
std::cerr << "Execution failed: " << WasmEdge_ResultGetMessage(Res2)
<< std::endl;
}
// 清理資源
WasmEdge_StringDelete(ProviderName);
WasmEdge_StringDelete(FuncName);
WasmEdge_VMDelete(VMCxt);
WasmEdge_ConfigureDelete(ConfCxt);
return 0;
}以上面來說,會有兩個 Module:
- 具名模組 (Named Module),在 Step 1 中,我們使用了
WasmEdge_VMRegisterModuleFromFile,不僅會建立一個實例,還會賦予它一個標籤 “provider” - 匿名模組 (Anonymous Module) ,在 Step 2 中,我們使用了
WasmEdge_VMRunWasmFromFile,WasmEdge 同樣會實例化這個模組,但不會給它名字,它被視為一個「臨時執行者」或「主程式」
在 WasmEdge 的執行時期架構中,每一個載入的 WebAssembly 模組在底層都會被實例化為一個 ModuleInstance 類別的物件。其中,具名模組會被註冊並儲存於 StoreManager 類別內部的成員變數中(通常是一個由名稱映射至實例的 std::map),以便進行後續的檢索與呼叫。

我們可以實際看 Log,如下:

多個匿名模組例子
如文件,在 0.10.0 版本,WasmEdge 新增支援多個匿名模組,因為你可能想,如果只使用 WasmEdge_VMRunWasmFromFile 這個「高階懶人包」API,確實一次只會剩下一個活動中的匿名模組(因為 VM 會在跑下一個之前,自動把前一個 ActiveMod 釋放掉),有了這個改動,你可以用更多匿名模組了。
#include <iostream>
#include <wasmedge/wasmedge.h>
int main() {
// 1. 環境初始化
WasmEdge_ConfigureContext *Conf = WasmEdge_ConfigureCreate();
WasmEdge_StoreContext *Store = WasmEdge_StoreCreate();
WasmEdge_ExecutorContext *Exec = WasmEdge_ExecutorCreate(Conf, NULL);
WasmEdge_LoaderContext *Loader = WasmEdge_LoaderCreate(Conf);
WasmEdge_ValidatorContext *Validator = WasmEdge_ValidatorCreate(Conf);
// 2. --- 關鍵:註冊具名模組 'provider' ---
WasmEdge_ASTModuleContext *AST_P = NULL;
WasmEdge_LoaderParseFromFile(Loader, &AST_P, "provider.wasm");
WasmEdge_ValidatorValidate(Validator, AST_P);
WasmEdge_String P_Name = WasmEdge_StringCreateByCString("provider");
WasmEdge_ModuleInstanceContext *P_Inst = NULL;
// 使用 Register API:這會實例化並把 "provider" 存進 Store 的 Map 裡
WasmEdge_ExecutorRegister(Exec, &P_Inst, Store, AST_P, P_Name);
std::cout << "Provider registered at: " << P_Inst << std::endl;
// 3. --- 載入 Consumer 藍圖 ---
WasmEdge_ASTModuleContext *AST_C = NULL;
WasmEdge_LoaderParseFromFile(Loader, &AST_C, "consumer.wasm");
WasmEdge_ValidatorValidate(Validator, AST_C);
// 4. --- 產生兩個匿名 Consumer 實例 ---
WasmEdge_ModuleInstanceContext *ModInstA = NULL;
WasmEdge_ModuleInstanceContext *ModInstB = NULL;
// 實例化 A:這時 Executor 會去 Store 找 "provider" 並成功連結
WasmEdge_ExecutorInstantiate(Exec, &ModInstA, Store, AST_C);
// 實例化 B:同樣連結到同一個 "provider" 實例
WasmEdge_ExecutorInstantiate(Exec, &ModInstB, Store, AST_C);
std::cout << "Anonymous Instance A (Consumer) at: " << ModInstA << std::endl;
std::cout << "Anonymous Instance B (Consumer) at: " << ModInstB << std::endl;
// 5. 執行 (略,同前一個範例)
// ... 執行 ModInstA 與 ModInstB 的 "run" ...
// 6. 清理
WasmEdge_ModuleInstanceDelete(ModInstA); // 匿名 A 消失,provider 引用數 -1
WasmEdge_ModuleInstanceDelete(ModInstB); // 匿名 B 消失,provider 引用數 -1
WasmEdge_ModuleInstanceDelete(P_Inst); // 具名 provider 消失
// ... 其他清理 ...
WasmEdge_StringDelete(P_Name);
return 0;
}結果如下:
kola:~/proj/WasmEdge/my_test$ LD_LIBRARY_PATH=../build/lib/api ./anonymous/ano
\Provider registered at: 0x6269b494e570
[2026-03-24 21:13:59.029] [info] >>> [Dependency Debug] Consumer '' is looking for Provider 'provider'
[2026-03-24 21:13:59.029] [info] >>> [Dependency Debug] Found Provider 'provider'! Registering edge...
[2026-03-24 21:13:59.029] [info] >>> [Dependency Debug] Consumer '' is looking for Provider 'provider'
[2026-03-24 21:13:59.029] [info] >>> [Dependency Debug] Found Provider 'provider'! Registering edge...
Anonymous Instance A (Consumer) at: 0x6269b4946860
Anonymous Instance B (Consumer) at: 0x6269b4947ae0如上,這次我們使用 WasmEdge_ExecutorInstantiate 註冊匿名模組,可以看到同時有兩個匿名模組,但使用同樣的 AST (WasmEdge_LoaderParseFromFile 產生 AST)
VM 其實是一個包裝器,他是一個高階 API 包裝像是 Executor, Module (匿名、命名), StoreManager … 等元件的包裝,像是我們在上面最簡單的例子使用 WasmEdge_VMXXX 函式。
但實際上我們也可以完全不用 VM,就是這邊多重匿名模組的例子,我們沒有 WasmEdge_VMContext,但就會需要自己建立 Loader, Validator 等物件,這代表你就是那台「虛擬機」的管理者,你必須手動安排 Parse -> Validate -> Instantiate 的流程。
我們可以看看 ModuleInstance 物件的內容。
多個模組
參考 Multiple WASM Module Example。
第一二三種:
- 例子 1:從檔案載入 (
WasmEdge_VMRegisterModuleFromFile) - 例子 2:從記憶體 Buffer 載入 (
WasmEdge_VMRegisterModuleFromBuffer) - 例子 3:從 AST (抽象語法樹) 載入 (
WasmEdge_VMRegisterModuleFromASTModule)
然後讓 Consumer 在 VM 裡面 Register 使用他。
寫法 4:使用者自己實例化,再借給 VM,所以最後要自己刪除模組。 寫法 5:完全不用 VM,只又 Executor,使用者自己建立了一個共用的 StoreCxt,然後用 Executor 分別把 lib.wasm 和 test.wasm 都 Instantiate 進同一個 Store 裡面,最後用 ExecutorInvoke 執行
ModuleInstance and StoreManager 原始碼
請參考 include/runtime/instance/module.h,他的成員基本上如下:
ModName(const std::string)- 具名模組:這裡存的是註冊名稱(如 “provider”)
- 匿名模組:這裡存的是空字串 ""
LinkedStore: 紀錄這個模組目前跟哪些 Store 關聯。當模組自毀(Destructor)時,它會透過這裡的 Callback 通知 Store- 資源所有權 (Owned Instances),成員如
OwnedFuncInsts,OwnedMemInsts,OwnedGlobInsts。這些是這一個模組自己定義並創造的資源。如果別人來 Import 這些東西,這個模組就是 Provider - 資源存取清單 (Imported & Added Instances),成員如
FuncInsts,MemInsts,GlobInsts等。這裡存的是 Raw Pointer。這份清單包含了「自己擁有的」加上「從別人那裡導入的(Imported)」所有資源 HostData外部的資料,有自己的 finalizerHostDataFinalizer
所以匿名跟具名的差別是,匿名的 ModName 是空字串,並且具名模組你可以在 StoreManager 裡面找到,所以匿名你要自己管理好,自己刪除,因為 StoreManager 不幫你管理。
在看一下他的一些重要函式(除了一些 get/add/find 函式):
-
Destructor,很特別
virtual ~ModuleInstance() noexcept { // When destroying this module instance, call the callbacks to unlink to the // store managers. // 遍歷 LinkedStore 容器中的每一個元素 // 每個 Pair first 是 StoreManager, Second 是 CallBack for (auto &&Pair : LinkedStore) { assuming(Pair.second); // 確保 Pair.second(回調函數)確實存在且有效 Pair.second(Pair.first, this); // 這個 CallBack 是傳參書 StoreManager and 自己 ModuleInstance } if (HostDataFinalizer.operator bool()) { // Finalizer HostDataFinalizer(HostData); } } -
linkStoreandunlinkStore: 就是設定LinkStore的地方
馬上來看 StoreManager,跟 ModuleInstace 有很大的關係,請參考 include/runtime/storemgr.h。
他有的成員像是:
Mutex: LockNamedMod: 主要的 Map 存所有 named module
可以看一下函式:
- Destructor 很特別,直接呼叫
reset(),也就是呼叫unlinkStore()所有ModuleInstace在NameMod裡面,這樣這些ModuleInstance在自己 destruct 的時候就不會自己再一次呼叫unlinkStore()對這個StorManager了 registerModule(): 基本上就是真的確認NameMod沒有他,並且加入他,然後給這個 namedModuleInstance一個 callback,這個 callback 基本上只是簡單的從NameMod刪除這個ModuleInstanceunregisterModule(): 相反的事情,跟reset(類似,從NameMod刪除他並且unlinkStore()
整體 VM 裡面的 Module 流程
#include <iostream>
#include <wasmedge/wasmedge.h>
int main() {
// --- Step 0: 初始化環境 ---
WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, nullptr);
WasmEdge_String ProviderName = WasmEdge_StringCreateByCString("provider");
std::cout << "--- Step 1: Register Named Module [provider] ---" << std::endl;
WasmEdge_Result Res =
WasmEdge_VMRegisterModuleFromFile(VMCxt, ProviderName, "provider.wasm");
if (!WasmEdge_ResultOK(Res)) {
std::cerr << "Failed to register provider.wasm: "
<< WasmEdge_ResultGetMessage(Res) << std::endl;
return -1;
}
std::cout
<< "\n--- Step 2: Load and Instantiate Anonymous Module [consumer] ---"
<< std::endl;
WasmEdge_ASTModuleContext *ASTCxt = nullptr;
WasmEdge_LoaderContext *LoadCxt = WasmEdge_VMGetLoaderContext(VMCxt);
// 1. 解析 consumer.wasm
Res = WasmEdge_LoaderParseFromFile(LoadCxt, &ASTCxt, "consumer.wasm");
if (!WasmEdge_ResultOK(Res)) {
std::cerr << "Failed to parse consumer.wasm" << std::endl;
return -1;
}
// 2. 將 AST 載入 VM
WasmEdge_VMLoadWasmFromASTModule(VMCxt, ASTCxt);
// --- [修正] 2.5 執行驗證 (Validate) ---
// 必須要有這一步,否則下一步 Instantiate 會回報 WrongVMWorkflow
Res = WasmEdge_VMValidate(VMCxt);
if (!WasmEdge_ResultOK(Res)) {
std::cerr << "Failed to validate consumer.wasm: "
<< WasmEdge_ResultGetMessage(Res) << std::endl;
return -1;
}
// 3. 執行實體化
Res = WasmEdge_VMInstantiate(VMCxt);
if (!WasmEdge_ResultOK(Res)) {
std::cerr << "Failed to instantiate consumer.wasm: "
<< WasmEdge_ResultGetMessage(Res) << std::endl;
return -1;
}
// 取得剛剛產生的匿名實例 (Active Module)
const WasmEdge_ModuleInstanceContext *ConsumerInst =
WasmEdge_VMGetActiveModule(VMCxt);
std::cout << "Consumer Instance (Anonymous) created at: " << ConsumerInst
<< std::endl;
// std::cout << "\n--- Step 3: Try to Force Delete [provider] while Consumer is "
// "alive ---"
// << std::endl;
// // 呼叫你新實作的 API
// WasmEdge_VMForceDeleteRegisteredModule(VMCxt, ProviderName);
// std::cout << "Note: Check spdlog output for deferred deletion." << std::endl;
std::cout << "\n--- Step 3.5: Execute Consumer code after ForceDelete ---"
<< std::endl;
// 1. 名字要改對,改成 "run"
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("run");
// 2. 準備兩個 i32 參數 (例如 10 + 20)
WasmEdge_Value Params[2] = {WasmEdge_ValueGenI32(10),
WasmEdge_ValueGenI32(20)};
WasmEdge_Value Returns[1];
// 3. 執行
Res = WasmEdge_VMExecute(VMCxt, FuncName, Params, 2, Returns, 1);
if (WasmEdge_ResultOK(Res)) {
std::cout << "Execution Success! Result: "
<< WasmEdge_ValueGetI32(Returns[0]) << std::endl;
std::cout << "This proves that even if 'provider' is logically deleted, "
"'consumer' can still use its memory."
<< std::endl;
} else {
std::cerr << "Execution Failed: " << WasmEdge_ResultGetMessage(Res)
<< std::endl;
}
WasmEdge_StringDelete(FuncName);
std::cout
<< "\n--- Step 4: Now Delete [consumer] to trigger cascading cleanup ---"
<< std::endl;
// WasmEdge_VMCleanup(VMCxt);
// --- Step 5: 清理環境 ---
std::cout << "\n--- Step 5: Final Cleanup ---" << std::endl;
WasmEdge_VMDelete(VMCxt);
WasmEdge_ConfigureDelete(ConfCxt);
WasmEdge_StringDelete(ProviderName);
WasmEdge_ASTModuleDelete(ASTCxt); // 注意:Load 完後 AST 通常可以提早刪除
std::cout << "Test finished successfully." << std::endl;
return 0;
}以上面的測試程式,流程包含:
注意可以看到在 delete ModuleInstance 的時候,callback 沒被呼叫,這是因為 StoreManager 先被刪除,他已經呼叫 reset() 把所有 link 刪除了。
VM 物件介紹
我們可以看看 VM,他是最大的物件,包含所有相關的成員,我們可以看一下原始碼 include/vm/vm.h,如下特別標示跟 Module 有關的成員:
{
...
/// \name VM environment.
/// @{
const Configure Conf;
Statistics::Statistics Stat;
VMStage Stage;
mutable std::shared_mutex Mutex;
/// @}
/// \name VM components.
/// @{
Loader::Loader LoaderEngine;
Validator::Validator ValidatorEngine;
Executor::Executor ExecutorEngine;
/// @}
/// \name VM Storage.
/// @{
/// Loaded AST module.
std::unique_ptr<AST::Module> Mod;
std::unique_ptr<AST::Component::Component> Comp;
/// Active module instance.
// 現在 Active 正要跑的的模組
std::unique_ptr<Runtime::Instance::ModuleInstance> ActiveModInst;
std::unique_ptr<Runtime::Instance::ComponentInstance> ActiveCompInst;
/// Registered module instances by user.
// 存放的所有模組
std::vector<std::unique_ptr<Runtime::Instance::ModuleInstance>> RegModInsts;
/// Built-in module instances mapped to the configurations. For WASI.
// 存放內建的 Host 模組(如 WASI)
std::unordered_map<HostRegistration,
std::unique_ptr<Runtime::Instance::ModuleInstance>>
BuiltInModInsts;
/// Loaded module instances from plug-ins.
// 存放從 Plug-ins 載入的 Host 模組(如 Tensorflow, Image 等)
std::vector<std::unique_ptr<Runtime::Instance::ModuleInstance>>
PlugInModInsts;
std::vector<std::unique_ptr<Runtime::Instance::ComponentInstance>>
PlugInCompInsts;
/// Self-owned store (nullptr if an outside store is assigned in constructor).
std::unique_ptr<Runtime::StoreManager> Store;
/// Reference to the store.
Runtime::StoreManager &StoreRef;
/// @}
};特別對最下面的 Store 有兩種情況:
- 情況 A (外部提供):如果你建立 VM 時傳入了一個現成的 Store,那 VM 就不擁有這個 Store。這時 Store (unique_ptr) 是 nullptr,而 StoreRef (引用) 指向外部那個 Store
- 情況 B (內部建立):如果你建立 VM 時沒給 Store,VM 會自己 new 一個。這時 Store (unique_ptr) 會持有它,而 StoreRef 同樣指向它
然後 RegModInsts 成員,他就是存放所有模組實體的地方,使用 unique_ptr 就表示他是唯一擁有的人,StoreManager 裡面也有 vector 存放他 (namedMod) 但那邊就只是用 raw pointer。
簡單來說,「擁有權」就是一份關於「誰該負責收屍(釋放記憶體)」的契約。
在 C 語言裡,確實沒有語法層面的擁有權概念;但在 C++ 中,這是一套保護系統不崩潰的核心機制。
在 C 語言中,當你執行 malloc(),系統只會給你一個位址(指標)。
- 誰都能刪:任何拿到這個位址的人都可以呼叫 free()
- 沒人負責:如果大家都以為對方會 free(),就會造成 Memory Leak(記憶體洩漏)
C++ 引入了 std::unique_ptr 來把這份「腦袋裡的契約」變成「強制執行的法律」。
- 獨佔性:一個物件在同一時間只能被一個 unique_ptr 擁有。
- 自動化:當這個 unique_ptr 消失(離開作用域或被刪除)時,它會自動呼叫 delete
- 禁止複製:你不能把 unique_ptr 複製給別人。如果你想把擁有權轉移,必須用 std::move(),這就像是辦理過戶,原主人會立刻失去權限
禁止複製:你不能把 unique_ptr 複製給別人。如果你想把擁有權轉移,必須用 std::move(),這就像是辦理過戶,原主人會立刻失去權限。
函式的話,我們可以看他們的 cleanup 函式
/// ======= Functions which are stageless. =======
/// Clean up VM status
void cleanup() {
std::unique_lock Lock(Mutex);
return unsafeCleanup();
}
void VM::unsafeCleanup() {
if (Mod) {
Mod.reset();
}
if (Comp) {
Comp.reset();
}
if (ActiveModInst) {
ActiveModInst.reset(); // 把 ActiveMod 真的 delete
}
if (ActiveCompInst) {
ActiveCompInst.reset();
}
StoreRef.reset(); // 清空 StoreManager 地圖而已
RegModInsts.clear(); // 把所有註冊模組真的刪除
Stat.clear();
unsafeLoadBuiltInHosts();
unsafeLoadPlugInHosts();
unsafeRegisterBuiltInHosts();
unsafeRegisterPlugInHosts();
LoaderEngine.reset();
Stage = VMStage::Inited;
}基本上就是原廠重製,呼叫不同的成員的 reset() 函式。