目錄

WasmEdge Module 介紹

簡介

簡單介紹 WasmEdge 裡面具名跟匿名模組的差別。

簡單例子

假設我們有兩個簡單的 Wasm 檔案:

  1. provider.wat: 提供一個 add 函式
  2. consumer.wat: 導入 (Import) provideradd 函式並有一個 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:

  1. 具名模組 (Named Module),在 Step 1 中,我們使用了 WasmEdge_VMRegisterModuleFromFile,不僅會建立一個實例,還會賦予它一個標籤 “provider”
  2. 匿名模組 (Anonymous Module) ,在 Step 2 中,我們使用了 WasmEdge_VMRunWasmFromFile,WasmEdge 同樣會實例化這個模組,但不會給它名字,它被視為一個「臨時執行者」或「主程式」

在 WasmEdge 的執行時期架構中,每一個載入的 WebAssembly 模組在底層都會被實例化為一個 ModuleInstance 類別的物件。其中,具名模組會被註冊並儲存於 StoreManager 類別內部的成員變數中(通常是一個由名稱映射至實例的 std::map),以便進行後續的檢索與呼叫。

/module/image.png

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

/module/image-1.png

多個匿名模組例子

URL
URL

如文件,在 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 外部的資料,有自己的 finalizer HostDataFinalizer

所以匿名跟具名的差別是,匿名的 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);
      }
    }
  • linkStore and unlinkStore: 就是設定 LinkStore 的地方


馬上來看 StoreManager,跟 ModuleInstace 有很大的關係,請參考 include/runtime/storemgr.h

他有的成員像是:

  • Mutex: Lock
  • NamedMod: 主要的 Map 存所有 named module

可以看一下函式:

  • Destructor 很特別,直接呼叫 reset(),也就是呼叫 unlinkStore() 所有 ModuleInstaceNameMod 裡面,這樣這些 ModuleInstance 在自己 destruct 的時候就不會自己再一次呼叫 unlinkStore() 對這個 StorManager
  • registerModule(): 基本上就是真的確認 NameMod 沒有他,並且加入他,然後給這個 named ModuleInstance 一個 callback,這個 callback 基本上只是簡單的從 NameMod 刪除這個 ModuleInstance
  • unregisterModule(): 相反的事情,跟 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;
}

以上面的測試程式,流程包含:

一開始會先註冊一些 WASM 內部的模組,都是 was 開頭的模組
一開始會先註冊一些 WASM 內部的模組,都是 was 開頭的模組
開始註冊我們自己的模組,有些有依賴就會找相關的模組引入,並且最後執行程式碼
開始註冊我們自己的模組,有些有依賴就會找相關的模組引入,並且最後執行程式碼
最後刪除 VM,會把所有東西刪除,包含 Module
最後刪除 VM,會把所有東西刪除,包含 Module

注意可以看到在 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() 函式。