Generated WAMR Helper Functions
The WIT code generator produces three files to simplify WAMR host application development:
<name>.hpp
- Component Model interface declarations (host and guest functions)<name>_wamr.hpp
- WAMR helper function declarations<name>_wamr.cpp
- WAMR symbol arrays and helper function implementations
Where <name>
is the output prefix you provide to wit-codegen
.
Usage
Simply include the WAMR header to access all helper functions:
#include "<name>_wamr.hpp"
This automatically includes:
<wamr.hpp>
- WAMR runtime API<cmcpp.hpp>
- Component Model C++ types"<name>.hpp"
- Your generated interface declarations
Generated Helper Functions
1. Registration Helpers
register_all_imports()
Registers all imported interfaces at once. Returns the number of functions registered, or -1 on failure.
Before:
for (const auto& reg : get_import_registrations()) {
bool success = wasm_runtime_register_natives_raw(reg.module_name, reg.symbols, reg.count);
if (!success) {
std::cerr << "Failed to register natives for: " << reg.module_name << std::endl;
return 1;
}
}
After:
int count = register_all_imports();
if (count < 0) {
std::cerr << "Failed to register native imports" << std::endl;
return 1;
}
std::cout << "Registered " << count << " host functions" << std::endl;
unregister_all_imports()
Unregisters all imported interfaces at cleanup time.
Before:
for (const auto& reg : get_import_registrations()) {
wasm_runtime_unregister_natives(reg.module_name, reg.symbols);
}
After:
unregister_all_imports();
2. WASM Runtime Constants
The generated WAMR bindings provide default configuration constants:
wasm_utils::DEFAULT_STACK_SIZE // 8192
wasm_utils::DEFAULT_HEAP_SIZE // 8192
3. Context Creation Functions (from <wamr.hpp>
)
The following helper functions are available in the cmcpp
namespace from <wamr.hpp>
(not generated, but part of the cmcpp library):
cmcpp::create_guest_realloc()
Creates a GuestRealloc
function from WAMR execution environment.
Usage:
wasm_function_inst_t cabi_realloc = wasm_runtime_lookup_function(module_inst, "cabi_realloc");
cmcpp::GuestRealloc realloc = cmcpp::create_guest_realloc(exec_env, cabi_realloc);
cmcpp::create_lift_lower_context()
Creates a complete LiftLowerContext
ready for use with guest_function<>()
.
Before:
wasm_memory_inst_t memory = wasm_runtime_lookup_memory(module_inst, "memory");
if (!memory) {
std::cerr << "Failed to lookup memory instance" << std::endl;
return 1;
}
uint8_t* mem_start_addr = (uint8_t*)wasm_memory_get_base_address(memory);
uint8_t* mem_end_addr = NULL;
wasm_runtime_get_native_addr_range(module_inst, mem_start_addr, NULL, &mem_end_addr);
GuestRealloc realloc = [exec_env, cabi_realloc](int original_ptr, int original_size, int alignment, int new_size) -> int {
uint32_t argv[4];
argv[0] = original_ptr;
argv[1] = original_size;
argv[2] = alignment;
argv[3] = new_size;
wasm_runtime_call_wasm(exec_env, cabi_realloc, 4, argv);
return argv[0];
};
LiftLowerOptions opts(Encoding::Utf8, std::span<uint8_t>(mem_start_addr, mem_end_addr - mem_start_addr), realloc);
LiftLowerContext ctx(trap, convert, opts);
After:
wasm_function_inst_t cabi_realloc = wasm_runtime_lookup_function(module_inst, "cabi_realloc");
LiftLowerContext ctx = cmcpp::create_lift_lower_context(module_inst, exec_env, cabi_realloc);
Parameters:
module_inst
: WAMR module instanceexec_env
: WAMR execution environmentcabi_realloc
: Thecabi_realloc
function from the WASM moduleencoding
: String encoding (default:Encoding::Utf8
)
Throws: std::runtime_error
if memory lookup fails
Example: Simplified main() Function
Here's how a typical main()
function looks using the generated helpers:
#include <wamr.hpp>
#include "test_wamr.hpp" // Includes test.hpp automatically
#include <iostream>
#include <fstream>
#include <filesystem>
int main(int argc, char** argv) {
// Initialize WAMR
wasm_runtime_init();
// Read WASM file (you need to implement this based on your application)
std::ifstream file("path/to/your.wasm", std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Failed to open WASM file" << std::endl;
return 1;
}
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
if (!file.read(buffer.data(), size)) {
std::cerr << "Failed to read WASM file" << std::endl;
return 1;
}
// Register all host functions
int count = register_all_imports();
if (count < 0) {
std::cerr << "Failed to register imports" << std::endl;
return 1;
}
std::cout << "Registered " << count << " host functions" << std::endl;
// Load and instantiate module
char error_buf[128];
wasm_module_t module = wasm_runtime_load(
(uint8_t*)buffer.data(), size, error_buf, sizeof(error_buf));
if (!module) {
std::cerr << "Failed to load module: " << error_buf << std::endl;
return 1;
}
wasm_module_inst_t module_inst = wasm_runtime_instantiate(
module, wasm_utils::DEFAULT_STACK_SIZE, wasm_utils::DEFAULT_HEAP_SIZE,
error_buf, sizeof(error_buf));
if (!module_inst) {
std::cerr << "Failed to instantiate: " << error_buf << std::endl;
wasm_runtime_unload(module);
return 1;
}
// Create execution environment and context
wasm_exec_env_t exec_env = wasm_runtime_create_exec_env(
module_inst, wasm_utils::DEFAULT_STACK_SIZE);
wasm_function_inst_t cabi_realloc = wasm_runtime_lookup_function(
module_inst, "cabi_realloc");
cmcpp::LiftLowerContext ctx = cmcpp::create_lift_lower_context(
module_inst, exec_env, cabi_realloc);
// Call guest functions using the generated typedefs
auto my_func = guest_function<guest::my_func_t>(
module_inst, exec_env, ctx, "example:sample/namespace#my-func");
auto result = my_func(/* args */);
// Cleanup
wasm_runtime_destroy_exec_env(exec_env);
unregister_all_imports();
wasm_runtime_deinstantiate(module_inst);
wasm_runtime_unload(module);
wasm_runtime_destroy();
return 0;
}
Benefits
- Reduced Boilerplate: Registration and cleanup happen in two function calls rather than manually iterating arrays
- Automatic Constants: Standard stack and heap sizes are provided as named constants
- Less Error-Prone: No manual copying of function names or managing registration state
- Type Safety: Generated function typedefs in the
guest::
namespace provide compile-time type checking - Separation of Concerns: Host interface definitions in
host::
namespace, guest signatures inguest::
namespace - Easier Maintenance: Changes to WIT automatically regenerate all registration code
Migration Guide
If you have existing code without generated helpers:
- Replace manual registration loops with a single call to
register_all_imports()
- Replace manual unregistration with
unregister_all_imports()
- Use generated constants
wasm_utils::DEFAULT_STACK_SIZE
andwasm_utils::DEFAULT_HEAP_SIZE
for consistency - Update namespace references: Host functions are in
host::
namespace, guest type signatures are inguest::
namespace - Use helper functions from wamr.hpp:
cmcpp::create_guest_realloc()
andcmcpp::create_lift_lower_context()
for context setup
Example Transformation
Before (manual registration):
// Manual array iteration
for (int i = 0; test_wamr_imports[i].symbol; i++) {
if (!wasm_runtime_register_natives(
"example:test/namespace",
&test_wamr_imports[i], 1)) {
// handle error
}
}
After (generated helpers):
// Single call
int count = register_all_imports();
if (count < 0) {
// handle error
}
Note on Root-Level Functions
Functions defined at the world level (not inside an interface) are included in the generated registration code if they have import statements. They appear in the host::
namespace just like interface functions:
// In your WIT file:
// package example:test;
//
// world test-world {
// import root-func: func(x: u32) -> u32;
//
// interface my-interface {
// import interface-func: func(s: string);
// }
// }
// Generated in test.hpp:
namespace host {
// Root-level function
cmcpp::u32_t root_func(cmcpp::u32_t x);
// Interface function
void interface_func(cmcpp::string_t s);
}
Both types of imports are automatically registered by register_all_imports()
. The namespace structure in the generated code is host::
for all imports and guest::
for all exports, regardless of whether they are at root level or in an interface.