Fluent Bit 内置 WAMR 的 wasm-c-api 嵌入指南:引擎初始化、线程模型与支持范围详解
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
导读
本文以 wasm-micro-runtime(WAMR,本仓库以lib/wasm-micro-runtime-WAMR-2.4.1/目录形式随 Fluent Bit 一起发布)中的 wasm-c-api 为主线,系统讲解这套引擎无关的 C 语言 WebAssembly 嵌入 API:它是什么、与 WAMR 原生 APIwasm_export.h如何二选一、线程模型有哪些硬性约束、核心对象如何串联(Engine → Store → Module → Instance → Func/Memory/Table/Global),并给出可直接编译运行的完整示例(hello、callback)以及当前版本的支持/不支持清单。读完本文,你将能够自行编写嵌入 WAMR 的 C 宿主程序,并规避线程与内存管理上的常见坑。
一、wasm-c-api 是什么
wasm-c-api 是一套与具体引擎无关的 WebAssembly 嵌入 API。它由 WebAssembly 社区维护的 wasm-c-api(约 910 行)。
这套 API 的定位与 WAMR 原生 APIwasm_export.h功能重叠:
- wasm-c-api(
wasm_c_api.h):引擎无关、面向可移植嵌入场景,类型与函数命名遵循标准(wasm_engine_*、wasm_store_*、wasm_module_*等); - WAMR 原生 API(
wasm_export.h,见 core/iwasm/include/wasm_export.h):功能更强、更贴近 WAMR 特性(AOT 加载、实例化参数、内存管理选项等)。
文档明确要求:嵌入方(embedder)应从中二选一,不要混用两套 API。从源码结构看,二者在 WAMR 内部共享同一套运行时对象(例如wasm_module_t与WASMModuleCommon直接互相关联,见 wasm_c_api.h),因此混用虽然"能编译",但会破坏所有权与生命周期约定,属于未定义行为。
二、所有权(own)与向量(vec)约定
wasm-c-api 中大量使用own限定符与wasm_xxx_vec_t向量类型,这是理解整套 API 内存语义的钥匙。
2.1own所有权语义
在 wasm_c_api.h 中,own是一个被定义为空的宏,纯粹用于文档标注:
own wasm_xxx_t*:拥有指针指向的数据;own wasm_xxx_t:所有权分布到结构体/联合体的所有字段;own wasm_xxx_vec_t:同时拥有向量本身及其元素;own函数参数:所有权从调用方移交给被调方;own函数返回值:所有权从被调方移交给调用方;- 例外:名为
out的own指针参数是"拷贝回写"输出参数,所有权从被调方交回调用方。
所有own数据都由wasm_xxx_new系列函数创建,并必须用对应的wasm_xxx_delete释放。注意:删除一个引用并不一定删除底层对象,它只是表示"该持有者不再使用它"。
2.2 向量类型
向量通过宏WASM_DECLARE_VEC(name, ptr_or_none)声明(wasm_c_api.h),结构体包含size(容量)、data(数据指针)、num_elems(当前元素数)、size_of_elem(元素字节大小)和lock字段。配套提供:
wasm_xxx_vec_new_empty:创建空向量;wasm_xxx_vec_new_uninitialized:按给定长度分配未初始化内存(读取二进制文件时常用);wasm_xxx_vec_new:从现有数组拷贝构造;wasm_xxx_vec_copy:深拷贝;wasm_xxx_vec_delete:释放。
wasm_byte_vec_t是字节向量(typedef byte_t wasm_byte_t),用于承载 wasm 二进制内容,同时被别名为wasm_name_t(模块名、导入/导出名等字符串均以字节向量表达,并提供wasm_name_new_from_string/wasm_name_new_from_string_nt便捷函数,见 wasm_c_api.h)。
三、运行时环境:Config、Engine、Store 与线程模型
3.1 Config:运行时配置
wasm_config_t允许在创建引擎前定制运行时行为(wasm_c_api.h):
| 字段 | 含义 | 默认值 |
|---|---|---|
mem_alloc_type | 内存分配器类型:Alloc_With_Pool(用户堆缓冲池)、Alloc_With_Allocator(用户自定义 malloc/realloc/free)、Alloc_With_System_Allocator(系统分配器) | Alloc_With_System_Allocator |
mem_alloc_option | 分配器选项联合体:pool分支含heap_buf/heap_size;allocator分支含malloc_func/realloc_func/free_func/user_data | 全 0 |
segue_flags | Linux x86_64 下是否用 GS 寄存器作为线性内存基址以加速 LLVM AOT/JIT 访问:bit0-bit4 控制 i32/i64/f32/f64/v128 load,bit8-bit12 控制 store;如0x01启用 i32.load,0x1F1F启用全部 load/store | 0 |
enable_linux_perf | 是否启用 Linux perf 支持 | false |
创建与定制入口:wasm_config_new()、wasm_config_set_mem_alloc_opt(config, type, option)、wasm_config_set_linux_perf_opt(config, bool)、wasm_config_set_segue_flags(config, flags)。
3.2 Engine 与 Store
wasm_engine_t* engine = wasm_engine_new(); // 默认配置 wasm_engine_t* engine = wasm_engine_new_with_config(cfg); // 定制配置 wasm_store_t* store = wasm_store_new(engine); // 由 engine 创建 store注意wasm_engine_new_with_args(mem_alloc_type_t, const MemAllocOption*)在 WAMR 中已被标记为WASM_API_DEPRECATED(见 wasm_c_api.h),新代码应使用wasm_config_t路线。
3.3 线程模型(原文档核心内容,务必遵守)
原文档在 FYI 中给出两条铁律:
- 一个
wasm_engine_t实例在单个进程内只能创建一次; - 每一个
wasm_store_t及其对象(module、instance、func 等)只能在单个线程中被访问。
同时,wasm_engine_new、wasm_engine_new_with_config、wasm_engine_new_with_args、wasm_engine_delete这些引擎级操作应当在线程安全环境中调用。源码进一步说明(wasm_c_api.h):如果平台具备 mutex 初始化能力,WAMR 会用全局锁保证这些操作线程安全;否则多线程同时执行引擎的 new/delete 时,开发者必须自行加锁。
原文档明确列出的不推荐行为(如确需使用,请保证合理的调用顺序):
- 在不同线程分别调用
wasm_engine_new与wasm_engine_delete; - 在不同线程多次调用
wasm_engine_new或wasm_engine_delete。
实践建议:在进程启动阶段单线程创建 engine 与 store,随后把不同的 store 分发给不同线程各自使用,这是 WAMR 多线程嵌入的典型架构。
四、核心对象与类型体系
4.1 值类型(Value Types)
wasm_valkind_t枚举(wasm_c_api.h):
| 常量 | 值 | 说明 |
|---|---|---|
WASM_I32/WASM_I64 | 0 / 1 | 32/64 位整数 |
WASM_F32/WASM_F64 | 2 / 3 | 32/64 位浮点 |
WASM_V128 | 4 | 128 位 SIMD 向量 |
WASM_EXTERNREF | 128 | 外部引用 |
WASM_FUNCREF | 129 | 函数引用 |
辅助判定:wasm_valkind_is_num(小于WASM_EXTERNREF为数值类型)、wasm_valkind_is_ref(大于等于WASM_EXTERNREF为引用类型)。
wasm_val_t是运行时值结构(wasm_c_api.h),包含kind字段与联合体of(i32/i64/f32/f64/ref)。
4.2 类型表示(Type Representations)
wasm_functype_t:wasm_functype_new(params, results)构造,wasm_functype_params/wasm_functype_results读取;wasm_globaltype_t:wasm_globaltype_new(valtype, mutability),可变性WASM_CONST/WASM_VAR;wasm_tabletype_t:wasm_tabletype_new(element, limits);wasm_memorytype_t:wasm_memorytype_new(limits),wasm_limits_t含min/max,上限默认wasm_limits_max_default = 0xffffffff;wasm_externtype_t:四种外部类型的统一抽象,wasm_externkind_t取值WASM_EXTERN_FUNC/WASM_EXTERN_GLOBAL/WASM_EXTERN_TABLE/WASM_EXTERN_MEMORY,并提供wasm_functype_as_externtype、wasm_externtype_as_functype等类型双向转换(含_const版本);wasm_importtype_t/wasm_exporttype_t:描述模块的导入/导出签名,分别携带 module 名、name 与类型。
4.3 Module 与 Instance
// 校验 + 编译 bool wasm_module_validate(store, &binary); own wasm_module_t* module = wasm_module_new(store, &binary); // 可选的模块命名(供调试) wasm_module_set_name(module, "hello"); const char* name = wasm_module_get_name(module); // 实例化(imports 为外部导入向量,第三个参数为 trap 输出) own wasm_instance_t* instance = wasm_instance_new(store, module, &imports, NULL); // 查询导入/导出 wasm_module_imports(module, &importtype_vec); wasm_module_exports(module, &exporttype_vec); wasm_instance_exports(instance, &exports);wasm_module_new_ex(store, binary, LoadArgs*)提供了更细粒度的加载控制(wasm_c_api.h),其语义对应wasm_export.h中的wasm_runtime_load_ex:
name:模块名;clone_wasm_binary:默认 true;若为 false,模块直接引用输入缓冲区而非克隆,加载完成后可释放原缓冲区;wasm_binary_freeable:仅 AOT/wasm loader 使用;no_resolve:默认 false;为 true 时不立即解析符号,需后续调用wasm_runtime_resolve_symbols。
4.4 运行对象
- Func:
wasm_func_new(store, functype, callback)创建宿主函数;wasm_func_new_with_env(store, functype, callback_with_env, env, finalizer)创建带环境闭包的宿主函数;wasm_func_call(func, args, results)同步调用并返回wasm_trap_t*(NULL 表示成功)。回调签名:own wasm_trap_t* (*wasm_func_callback_t)(const wasm_val_vec_t* args, own wasm_val_vec_t* results); - Global:
wasm_global_new/wasm_global_get/wasm_global_set; - Table:
wasm_table_new/wasm_table_get/wasm_table_set/wasm_table_size/wasm_table_grow; - Memory:
wasm_memory_new/wasm_memory_data(返回线性内存首字节指针)/wasm_memory_data_size/wasm_memory_size(页数,一页MEMORY_PAGE_SIZE = 0x10000即 64 KiB)/wasm_memory_grow; - Extern:
wasm_func_as_extern/wasm_extern_as_func等"对象 ↔ 外部抽象"转换,是组装 imports 向量与解析 exports 的必经之路; - Trap:
wasm_trap_new/wasm_trap_message/wasm_trap_origin/wasm_trap_trace(获取调用栈帧wasm_frame_t序列); - Foreign:
wasm_foreign_new创建宿主侧任意对象句柄,可随引用类型传入 wasm。
宿主信息挂钩:所有引用对象都支持wasm_xxx_get_host_info/wasm_xxx_set_host_info/wasm_xxx_set_host_info_with_finalizer(wasm_c_api.h),方便把自定义数据结构挂到运行时对象上。
五、完整可运行示例:hello 与 callback
WAMR 在 samples/wasm-c-api/ 下提供了完整示例(每个示例同时附.c宿主源码与.wat手写 wasm 源文件)。
5.1 hello:最小嵌入流程
src/hello.c 完整演示了 wasm-c-api 的标准生命周期,值得逐段研读:
- 初始化:
wasm_engine_new()→wasm_store_new(engine); - 读取二进制:以
fopen打开hello.wasm(AOT-only 构建时打开hello.aot),fseek/ftell获取长度,wasm_byte_vec_new_uninitialized分配并fread读入; - 校验与编译:
wasm_module_validate(store, &binary)校验,wasm_module_new(store, &binary)编译,随后wasm_byte_vec_delete(&binary)释放二进制; - 创建宿主回调:
wasm_functype_new_0_0()声明"无参无返回值"函数类型,wasm_func_new绑定hello_callback(其内部打印Calling back...与> Hello World!); - 实例化:
wasm_func_as_extern(hello_func)包装为 extern 数组,WASM_ARRAY_VEC(externs)组装导入向量,wasm_instance_new(store, module, &imports, NULL); - 提取导出:
wasm_instance_exports(instance, &exports),wasm_extern_as_func(exports.data[0])拿到导出函数; - 调用:
wasm_func_call(run_func, &args, &results)(空参数、空结果),trap 非空则报错; - 清理:依次
wasm_extern_vec_delete、wasm_store_delete、wasm_engine_delete。
5.2 callback:带参回调与环境闭包
src/callback.c 展示了两种宿主函数形态:
- 普通回调:
print_callback读取args->data[0]打印 wasm 传来的值,并通过wasm_val_copy(&results->data[0], &args->data[0])将入参原样返回; - 带环境闭包:
closure_callback(void* env, ...)从env中取出宿主侧整数i(示例中为 42),把i写入结果results->data[0].of.i32;创建时用wasm_func_new_with_env(store, closure_type, closure_callback, &i, NULL)把&i作为环境传入。
调用侧使用wasm_val_t as[2] = { WASM_I32_VAL(3), WASM_I32_VAL(4) }构造参数向量,调用后从rs[0].of.i32读取返回值。回调函数的wasm_val_print帮助函数还演示了如何按kind分派打印 i32/i64/f32/f64/引用类型。
其余示例还覆盖了 memory(线性内存读写)、table(表操作与函数指针)、global(全局变量导入导出)、multi(多模块)、trap(异常与调用栈)、hostref(宿主引用)、reflect(反射)、serialize(序列化)、threads(线程)等场景,均在 samples/wasm-c-api/src/ 下。
六、编译与运行示例
示例工程的构建说明见 samples/wasm-c-api/README.md。前提是下载并安装 WABT(WebAssembly Binary Toolkit)工具链,用于把.wat文本编译为.wasm二进制。
解释器模式(默认):
$ mkdir build && cd build $ cmake .. $ make # 构建出带 c-api 支持的运行时库 # 同时把 ../src/ 下的 *.wasm 拷贝到当前目录,并生成可执行文件 $ ./hello $ ./callback $ ./globalAOT 模式:通过编译开关切换(见 samples/wasm-c-api/CMakeLists.txt,默认WAMR_BUILD_INTERP=1、WAMR_BUILD_AOT=0):
$ mkdir build && cd build $ cmake -DWAMR_BUILD_INTERP=0 -DWAMR_BUILD_AOT=1 .. $ make # 同样构建带 c-api 支持的库,并把 *.wasm 转为 *.aot 后运行 $ ./hello也就是说,同一套 wasm-c-api 宿主代码在解释器与 AOT 两种模式下均可工作;示例宿主源码通过#if WASM_ENABLE_AOT != 0 && WASM_ENABLE_INTERP == 0判断加载*.aot还是*.wasm文件。AOT 模式可获得更快的启动与执行性能,但需要先用 wamrc 等工具把 wasm 编译为 AOT 镜像,这一取舍与 doc/build_wamr.md、doc/embed_wamr.md 中描述的构建策略一致。
七、当前版本的不支持清单(unupported list)
原文档明确指出:WAMR 支持 wasm-c-api 的绝大多数 API,以下是当前版本(WAMR 2.4.1)明确不支持的部分。
7.1 References 共享/获取
WASM_API_EXTERN own wasm_shared_##name##_t* wasm_##name##_share(const wasm_##name##_t*); WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_obtain(wasm_store_t*, const wasm_shared_##name##_t*);wasm_module_share/wasm_module_obtain等跨 store 共享引用对象的 API 不可用。从源码看,声明中module被注释为WASM_DECLARE_SHARABLE_REF(module)(见 wasm_c_api.h),且typedef wasm_module_t wasm_shared_module_t仅是别名,实际并未提供跨线程/跨 store 的共享语义。这与线程模型(每个 store 单线程访问)是自洽的。
7.2 Module 序列化/反序列化
WASM_API_EXTERN void wasm_module_serialize(const wasm_module_t*, own wasm_byte_vec_t* out); WASM_API_EXTERN own wasm_module_t* wasm_module_deserialize(wasm_store_t*, const wasm_byte_vec_t*);wasm_module_serialize/wasm_module_deserialize未实现。需要模块级缓存/预编译的场景,应改用 WAMR 原生 API(wasm_export.h)中的 AOT 编译与wasm_runtime_load系列接口,或用wasm_module_new_ex的LoadArgs精细控制加载行为。
7.3 Table 与 Memory 的宿主侧增长
原文档特别强调:通过 wasm 操作码增长 table 或 memory 是受支持的,但通过宿主侧函数调用增长则不支持:
WASM_API_EXTERN bool wasm_table_grow(wasm_table_t*, wasm_table_size_t delta, wasm_ref_t* init); WASM_API_EXTERN bool wasm_memory_grow(wasm_memory_t*, wasm_memory_pages_t delta);wasm_table_grow与wasm_memory_grow在 wasm-c-api 层不可用。这意味着:
- 若 wasm 模块内部通过
memory.grow/table.grow指令扩容,正常运行; - 若宿主代码期望主动扩容 wasm 线性内存或表,需要通过其他机制(例如在 wasm 侧导出增长函数再由宿主调用),或直接改用
wasm_export.h原生 API。
八、与 wasm_export.h 原生 API 的选型建议
结合 doc/wasm_c_api.md 与头文件内容,两者差异可以归纳为:
| 维度 | wasm-c-api(wasm_c_api.h) | 原生 API(wasm_export.h) |
|---|---|---|
| 定位 | 引擎无关、跨运行时可移植 | WAMR 专属、特性最全 |
| 类型系统 | 统一wasm_xxx_t抽象(valtype/functype/externtype…) | 直接面向wasm_module_t、wasm_function_inst_t等具体对象 |
| 所有权约定 | own标注明确 | 依赖函数约定 |
| AOT/加载细节 | wasm_module_new_ex+LoadArgs | wasm_runtime_load_ex、实例化参数完整 |
| 序列化/共享 | 不支持(见上文) | 支持 AOT 镜像等 |
| 增长内存/表 | 宿主侧不支持 | 支持 |
选型建议:追求"一份宿主代码可换引擎"的嵌入式/跨平台场景优先选 wasm-c-api;需要 WAMR 特有优化(AOT 镜像、快速解释器、SGX、自定义内存池细分控制)或需要用到上节不支持清单中能力的场景,选择wasm_export.h。在 Fluent Bit 生态中,WAMR 以 lib/wasm-micro-runtime-WAMR-2.4.1/ 形式内嵌于仓库,其 wasm 相关能力即通过该运行时对外提供,宿主插件在集成时同样面临上述两套 API 的选择。
九、小结
wasm-c-api 为 WAMR 提供了一条标准化的嵌入路径。使用时的关键要点可归纳为:
- 二选一:不要混用
wasm_c_api.h与wasm_export.h; - 线程纪律:engine 每进程只建一次;每个 store 及其对象单线程访问;engine 的 new/delete 需要线程安全环境;
- 所有权纪律:
own标注的对象必须配对 delete,向量用wasm_xxx_vec_delete释放; - 标准流程:engine → store → 读二进制 → validate → module_new →(组装 imports)→ instance_new → 取 exports → func_call → 逆序清理;
- 能力边界:References 共享、Module 序列化、宿主侧 table/memory grow 当前不支持,遇到相关需求请切换原生 API 或调整设计。
对于需要进一步深挖的读者,仓库内 samples/wasm-c-api/src/ 下的 14 组示例、core/iwasm/common/wasm_c_api_internal.h 中的内部实现,以及 doc/embed_wamr.md、doc/embed_wamr_spawn_api.md 都是继续深入的良好起点。
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考