
wasm2c 实战指南用 WABT 把 WebAssembly 模块转换为可移植的 C 源码【免费下载链接】wabtThe WebAssembly Binary Toolkit项目地址: https://gitcode.com/GitHub_Trending/wa/wabtwasm2c是 WebAssembly Binary ToolkitWABT中的核心工具之一它读取一个 WebAssembly 二进制模块.wasm并将其翻译为功能等价、可直接编译运行的 C 源码与头文件。本文以 WABT 仓库中的 wasm2c/README.md 为骨架结合 src/tools/wasm2c.cc 入口实现、wasm2c/wasm-rt.h 运行时头文件与 wasm2c/examples 真实示例完整讲解从.wat编写、.wasm编译到.c落地、链接运行的端到端流程并深入剖析生成代码的结构、嵌入方embedder必须实现的运行时符号、异常处理支持、多实例化以及 Segue 段寄存器优化等进阶主题。读完本文你将能独立完成“Wasm 模块 → C 代码 → 本地可执行程序”的完整转换并理解 wasm2c 运行时契约的每个细节。wasm2c 是什么wasm2c接收一个 WebAssembly 模块生成与之等价的 C 源码和头文件。生成代码的目标标准为 C99如果模块使用了 Wasm 线程/原子操作threads/atomics则生成代码面向 C11 标准。这种转换使 Wasm 模块可以被嵌入到任何支持 C 编译器的宿主程序中无需解释器或 JIT 运行时编译产物直接以机器码形式运行。最基本的用法# 解析二进制文件 test.wasm写出 test.c 和 test.h $ wasm2c test.wasm -o test.c # 解析 test.wasm写出 test.c 和 test.h但忽略二进制中的调试名字段如有 $ wasm2c test.wasm --no-debug-names -o test.c两个命令都会同时产生test.c与test.h两个文件-o指定 C 源文件路径头文件路径自动由去除扩展名后拼接.h得到该逻辑见 src/tools/wasm2c.cc。命令行选项全解结合 src/tools/wasm2c.cc 的参数解析与 man/wasm2c.1 手册页wasm2c支持的完整选项如下选项说明-h, --help打印帮助信息--version打印版本信息-v, --verbose输出更多调试信息可多次使用-o, --outputFILENAME生成的 C 源文件路径默认输出到 stdout--num-outputsNUM生成的 C 源文件数量分片输出见下文-n, --module-nameMODNAME生成的 C 符号统一前缀默认取 names section 中的模块名若无则取输入文件名--no-debug-names忽略二进制文件中的调试名--disable-exceptions禁用实验性异常处理exception handling--disable-mutable-globals禁用导入/导出可变全局变量--disable-saturating-float-to-int禁用饱和浮点转整数指令--disable-sign-extension禁用符号扩展指令--disable-simd禁用 SIMD 支持--enable-threads启用线程支持Wasm threads/atomics--enable-function-references启用类型化函数引用--disable-multi-value禁用多返回值--disable-tail-call禁用尾调用--disable-bulk-memory禁用批量内存操作--disable-reference-types禁用引用类型externref--disable-annotations禁用自定义注解语法--enable-code-metadata启用代码元数据--enable-gc启用垃圾回收--disable-memory64禁用 64 位内存--disable-multi-memory禁用多内存--disable-extended-const禁用扩展常量表达式--disable-relaxed-simd禁用 Relaxed SIMD--enable-custom-page-sizes启用自定义页大小--enable-compact-imports启用紧凑导入段--enable-wide-arithmetic启用宽算术--enable-all启用全部特性几个值得注意的细节均可在源码中验证模块名决定符号前缀wasm2c.cc中若未显式指定-n则依次回退使用 names section 中的模块名、输入文件名去扩展名。所有生成的导出符号都共享这一前缀。多文件输出--num-outputsNUM可将生成代码分片写入basename_0.c、basename_1.c…同时额外生成basename-impl.h见 src/tools/wasm2c.cc适合超大模块并行编译。特性白名单校验wasm2c.cc维护了supported_features列表multi-memory、multi-value、sign-extension、saturating-float-to-int、exceptions、memory64、extended-const、simd、threads、tail-call、custom-page-sizes、compact-imports见 src/tools/wasm2c.cc若启用了列表之外的功能会直接报错退出。内部流水线Wasm2cMain依次执行ReadBinaryIr二进制 → IR、ValidateModule校验、GenerateNames/ApplyNames生成与套用名字最后调用WriteC写出 C 代码见 src/tools/wasm2c.cc。教程.wat - .wasm - .c以一个经典的阶乘函数为例将它保存为fac.wat(memory $mem 1) (func (export fac) (param $x i32) (result i32) (if (result i32) (i32.eq (local.get $x) (i32.const 0)) (then (i32.const 1)) (else (i32.mul (local.get $x) (call 0 (i32.sub (local.get $x) (i32.const 1)))) ) ) )先用wat2wasm把文本格式编译为二进制格式$ wat2wasm fac.wat -o fac.wasm再用wasm2c把二进制转换为 C 源文件与头文件$ wasm2c fac.wasm -o fac.c这一步生成两个文件fac.c与fac.h。仓库 wasm2c/examples/fac 下保存了这一整套流程的完整产物fac.wat、fac.wasm、fac.c、fac.h、main.c和 Makefile其中 Makefile 展示了自动化构建方式fac.wasm: fac.wat ../../../bin/wat2wasm ../../../bin/wat2wasm $ -o $ fac.c: fac.wasm ../../../bin/wasm2c ../../../bin/wasm2c $ -o $ --disable-simd使用生成的模块为实际使用fac模块新建main.c包含fac.h、初始化模块实例并调用fac。wasm2c依据fac.wasm生成若干 C 符号w2c_fac表示fac模块一个实例的类型wasm2c_fac_instantiate/wasm2c_fac_free构造与释放w2c_fac实例的函数w2c_fac_fac模块导出的fac函数本身作用于w2c_fac实例。所有导出符号共享同一个模块 IDfac默认取自模块 names section 或输入文件名可通过-n/--module-name覆盖。#include stdio.h #include stdlib.h #include fac.h int main(int argc, char** argv) { /* Make sure there is at least one command-line argument. */ if (argc 2) { printf(Invalid argument. Expected %s NUMBER\n, argv[0]); return 1; } /* Convert the argument from a string to an int. Well implicitly cast the int to a u32, which is what fac expects. */ u32 x atoi(argv[1]); /* Initialize the Wasm runtime. */ wasm_rt_init(); /* Declare an instance of the fac module. */ w2c_fac fac; /* Construct the module instance. */ wasm2c_fac_instantiate(fac); /* Call fac, using the mangled name. */ u32 result w2c_fac_fac(fac, x); /* Print the result. */ printf(fac(%u) - %u\n, x, result); /* Free the fac module. */ wasm2c_fac_free(fac); /* Free the Wasm runtime state. */ wasm_rt_free(); return 0; }这段代码展示了嵌入方宿主程序的标准生命周期先wasm_rt_init()初始化运行时声明并instantiate模块实例调用导出函数最后依次wasm2c_fac_free与wasm_rt_free释放资源。编译 wasm2c 输出编译可执行程序需要main.c、生成的fac.c再加上运行时实现文件wasm-rt-impl.c和wasm-rt-mem-impl.c其中实现了fac.c/fac.h用到的各类wasm_rt_*函数$ cc -o fac main.c fac.c wasm2c/wasm-rt-impl.c wasm2c/wasm-rt-mem-impl.c -Iwasm2c -lm示例的 Makefile 中对应规则为fac: main.o fac.o ../../wasm-rt-impl.o并链接-lm数学库。关于编译优化的合规性提示wasm2c 依赖 C 编译器的某些行为来维持与 WebAssembly 规范的严格一致性尤其涉及两点将“signaling”NaN 转换为“quiet”NaN 浮点值无限递归必须产生 trap。因此在使用优化编译如-O2/-O3时需要禁用部分优化以保持合规GCC 11 上追加-fno-optimize-sibling-calls -frounding-math -fsignaling-nans即可clang 14 上追加-fno-optimize-sibling-calls -frounding-math即可。编译并运行验证$ ./fac 1 fac(1) - 1 $ ./fac 5 fac(5) - 120 $ ./fac 10 fac(10) - 3628800开启额外的健全性检查wasm2c提供了宏WASM_RT_SANITY_CHECKS一旦定义生成的 wasm2c 代码会启用额外的健全性检查。注意这会带来较高的性能开销因此仅建议在 debug 构建中使用wasm2c/wasm-rt.h 中默认值为 0。开启 Segue 优化Linux x86_64 专属wasm2c在条件允许时可以使用 “Segue” 优化借助 x86 段寄存器保存 Wasm 线性内存的位置从而加速内存访问。其启用前提是使用 clang 编译 wasm2c 输出、运行在 x86_64 Linux 上、定义了宏WASM_RT_ALLOW_SEGUE并向 clang 传递-mfsgsbase标志。从 wasm2c/wasm-rt.h 的WASM_RT_USE_SEGUE推导逻辑可见完整约束模块形态限制模块必须恰好使用一块非共享、默认页大小、32 位、被导入或导出的内存编译器限制不能用 GCC 编译。Segue 需要(rd|wr)gsbase内建函数、“address namespaces”指针访问以及对自定义 “address namespaces” 指针的 memcpy 支持GCC 不满足 memcpy 要求故目前仅 clang 9 可用平台限制不能在 Windows 上使用因为 Windows 在上下文切换时不会恢复段寄存器其余条件非大端!WABT_BIG_ENDIAN、非 Android、运行于 Linux 或 FreeBSD。wasm2c生成的代码会在调用进入 wasm2c 生成的模块时自动设置未使用的段寄存器x86_64 Linux 上为%gs在调用外部模块后恢复。普通 C 编写的宿主函数无需改动即可继续工作——C 代码不会修改空闲的%gs段寄存器但任何用汇编编写、会破坏空闲段寄存器的宿主函数必须在交还控制权给 wasm2c 生成代码之前恢复该寄存器的值。进一步的优化如果宿主程序不把%gs段寄存器用于其他任何目的多数程序如此可以定义宏WASM_RT_SEGUE_FREE_SEGMENT允许 wasm2c 无条件覆写%gs而无需恢复旧值。WASM_RT_USE_SEGUE生效时运行时还需要提供wasm_rt_fsgsbase_inst_supported变量以及wasm_rt_syscall_set_segue_base/wasm_rt_syscall_get_segue_base函数见 wasm2c/wasm-rt.h。可以用 Dhrystone 基准对比 Segue 开启前后的性能差异cd wasm2c/benchmarks/segue make实际生成的 fac.c 顶部也展示了 Segue 相关的条件编译只有当WASM_RT_USE_SEGUE IS_SINGLE_UNSHARED_MEMORY时才启用WASM_RT_USE_SEGUE_FOR_THIS_MODULE并通过__builtin_ia32_rdgsbase64/wrgsbase64或系统调用读写段基址。查看生成的头文件 fac.h生成的 fac.h 大致如下/* Automatically generated by wasm2c */ #ifndef FAC_H_GENERATED_ #define FAC_H_GENERATED_ ... #include wasm-rt.h ... #ifndef WASM_RT_CORE_TYPES_DEFINED #define WASM_RT_CORE_TYPES_DEFINED ... #endif #ifdef __cplusplus extern C { #endif typedef struct w2c_fac { char dummy_member; } w2c_fac; void wasm2c_fac_instantiate(w2c_fac*); void wasm2c_fac_free(w2c_fac*); wasm_rt_func_type_t wasm2c_fac_get_func_type(uint32_t param_count, uint32_t result_count, ...); /* export: fac */ u32 w2c_fac_fac(w2c_fac*, u32); #ifdef __cplusplus } #endif #endif /* FAC_H_GENERATED_ */逐段解读最外层的#ifndef是头文件的标准防重复包含样板WASM_RT_CORE_TYPES_DEFINED段包含所有 WebAssembly 模块都需要的类型定义实际生成文件中是u8/s8/u16/s16/u32/s32/u64/s64/f32/f64这些定宽类型别名extern C保证该头文件被 C 包含时符号不被名字修饰name mangling。fac模块没有全局变量、内存或表因此w2c_fac结构体近乎为空只有一个dummy_member。wasm-rt.h 中的核心类型头文件包含的 wasm2c/wasm-rt.h 定义了大量与 WebAssembly 相关的运行时类型。首先是wasm_rt_trap_t枚举用于说明 trap 发生的原因typedef enum { WASM_RT_TRAP_NONE, WASM_RT_TRAP_OOB, WASM_RT_TRAP_INT_OVERFLOW, WASM_RT_TRAP_DIV_BY_ZERO, WASM_RT_TRAP_INVALID_CONVERSION, WASM_RT_TRAP_UNREACHABLE, WASM_RT_TRAP_CALL_INDIRECT, WASM_RT_TRAP_UNCAUGHT_EXCEPTION, WASM_RT_TRAP_EXHAUSTION, } wasm_rt_trap_t;源码 wasm2c/wasm-rt.h 中的完整版本还包含WASM_RT_TRAP_NULL_REF空引用与WASM_RT_TRAP_UNALIGNED非对齐原子操作在 macOS 等启用信号处理栈检测溢出的平台上WASM_RT_TRAP_EXHAUSTION会与WASM_RT_TRAP_OOB合并为同一个值WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS。其次是wasm_rt_type_t枚举用于描述函数签名。README 列出六种 WebAssembly 值类型typedef enum { WASM_RT_I32, WASM_RT_I64, WASM_RT_F32, WASM_RT_F64, WASM_RT_FUNCREF, WASM_RT_EXTERNREF, } wasm_rt_type_t;当前 wasm2c/wasm-rt.h 的实现还追加了WASM_RT_V128SIMD 向量与WASM_RT_EXNREF异常引用。接着是wasm_rt_function_ptr_t——通用函数回调的签名。由于 Wasm 表可以容纳任意签名的函数需要将其统一转换为规范形式typedef void (*wasm_rt_function_ptr_t)(void);接下来是函数引用funcref的定义。在 WebAssembly 1.0 中这是所有表元素的类型如今 funcref 也可以作为普通值使用表也可以声明为 externref 类型。结构体中wasm_rt_func_type_t是一个不透明的 256 位 ID可通过Z_[modname]_get_func_type函数查询callback示例对此有演示module_instance指向函数所属模块实例的指针调用该函数时会被传入typedef struct { wasm_rt_func_type_t func_type; wasm_rt_function_ptr_t func; void* module_instance; } wasm_rt_funcref_t;当前源码 wasm2c/wasm-rt.h 中func_type实际以const char*实现并新增了func_tailcallee成员用于尾调用优化。然后是内存实例的定义。data指向size字节的线性内存size是内存实例当前大小字节pages是当前大小页数page_size是页大小默认 65536 字节max_pages是模块指定的最大页数或内存索引类型所允许的上限is64为 true 表示内存可增长到 2^64 字节false 表示限制在 2^32 字节typedef struct { uint8_t* data; uint32_t page_size; uint64_t pages, max_pages; uint64_t size; bool is64; } wasm_rt_memory_t;当前源码 wasm2c/wasm-rt.h 还增加了data_end字段用于大端平台上地址翻转访问及守卫页guard page布局。紧跟着的是共享内存实例的定义它与普通内存类似但可被多个 Wasm 实例使用因此对操作施加了最低限度的内存序约束共享内存定义多出一个成员mem_lock用于内存增长操作时的线程安全锁typedef struct { _Atomic volatile uint8_t* data; uint64_t pages, max_pages; uint64_t size; bool is64; mtx_t mem_lock; } wasm_rt_shared_memory_t;源码中的mem_lock实际类型为WASM_RT_MUTEXWindows 上为CRITICAL_SECTION其他平台为pthread_mutex_t且仅在 C11 可用WASM_RT_C11_AVAILABLE时定义见 wasm2c/wasm-rt.h。最后是表实例的定义。data指向size个元素与内存实例类似size是表的当前大小max_size是最大大小若无上限则为0xfffffffftypedef struct { wasm_rt_funcref_t* data; uint32_t max_size; uint32_t size; } wasm_rt_funcref_table_t;源码还提供了对应的wasm_rt_externref_table_t元素类型为wasm_rt_externref_t即void*见 wasm2c/wasm-rt.h。嵌入方必须定义的符号wasm-rt.h中还有一组函数声明必须在这些 C 源码被使用之前由嵌入方即你实现。这些函数的 C 实现定义在 wasm2c/wasm-rt-impl.h 与 wasm2c/wasm-rt-impl.c 中void wasm_rt_init(void); bool wasm_rt_is_initialized(void); void wasm_rt_free(void); void wasm_rt_trap(wasm_rt_trap_t) __attribute__((noreturn)); const char* wasm_rt_strerror(wasm_rt_trap_t trap); void wasm_rt_allocate_memory(wasm_rt_memory_t*, uint32_t initial_pages, uint32_t max_pages, bool is64, uint32_t page_size); uint32_t wasm_rt_grow_memory(wasm_rt_memory_t*, uint32_t pages); void wasm_rt_free_memory(wasm_rt_memory_t*); void wasm_rt_allocate_memory_shared(wasm_rt_shared_memory_t*, uint32_t initial_pages, uint32_t max_pages, bool is64, uint32_t page_size); uint32_t wasm_rt_grow_memory_shared(wasm_rt_shared_memory_t*, uint32_t pages); void wasm_rt_free_memory_shared(wasm_rt_shared_memory_t*); void wasm_rt_allocate_funcref_table(wasm_rt_table_t*, uint32_t elements, uint32_t max_elements); void wasm_rt_allocate_externref_table(wasm_rt_externref_table_t*, uint32_t elements, uint32_t max_elements); void wasm_rt_free_funcref_table(wasm_rt_table_t*); void wasm_rt_free_externref_table(wasm_rt_table_t*); uint32_t wasm_rt_call_stack_depth; /* on platforms that dont use the signal handler to detect exhaustion */ void wasm_rt_init_thread(void); void wasm_rt_free_thread(void);当前源码 wasm2c/wasm-rt.h 中内存相关函数的initial_pages/max_pages参数已改为uint64_t返回值也相应为uint64_t以支持 memory64。各符号语义如下wasm_rt_init必须在做任何其他事情之前调用用于初始化运行时wasm_rt_free释放所有全局状态wasm_rt_is_initialized用于确认运行时已初始化。wasm_rt_trap模块发生 trap 时调用的函数。可能的实现方式有抛出 C 异常或直接中止程序执行。wasm2c 自带的默认运行时使用longjmp展开栈。宿主可以通过定义WASM_RT_TRAP_HANDLER覆盖对longjmp的调用指向自定义 trap 处理函数签名须为void handler(wasm_rt_trap_t)例如-DWASM_RT_TRAP_HANDLERmy_trap_handler。wasm_rt_allocate_memory初始化内存实例至少分配给定初始页数所需空间每页大小为page_size除非使用 custom-page-sizes 特性否则必须为WASM_DEFAULT_PAGE_SIZE即 64 KiB内存必须清零is64参数指示内存以 i32 还是 i64 地址索引。wasm_rt_grow_memory按给定页数增长内存实例。若内存不足或新页数超过最大页数则必须返回0xffffffff表示失败成功时返回内存实例之前的页数。宿主可通过定义WASM_RT_GROW_FAILED_HANDLER指定失败回调签名为void handler(void)例如-DWASM_RT_GROW_FAILED_HANDLERmy_growfail_handler。wasm_rt_free_memory释放内存实例。wasm_rt_allocate_memory_shared初始化可被不同 Wasm 线程共享的内存实例其余行为与wasm_rt_allocate_memory类似wasm_rt_grow_memory_shared按页增长共享内存其余类似wasm_rt_grow_memorywasm_rt_free_memory_shared释放共享内存实例。wasm_rt_allocate_funcref_table与wasm_rt_allocate_externref_table初始化对应类型的表实例至少分配给定初始元素数的空间元素必须清零对应的wasm_rt_free_*_table释放表实例。wasm_rt_call_stack_depth当前调用栈深度。由于它在模块间共享只能由嵌入方定义一次且仅在不使用信号处理来检测栈溢出的平台上使用。wasm_rt_init_thread/wasm_rt_free_thread初始化/释放除调用wasm_rt_init的线程之外的其他线程的运行时状态示例见 wasm2c/examples/threads。异常处理exceptions的运行时支持若 wasm2c 以异常支持模式运行还需定义若干附加符号若想避免可用--disable-exceptions。它们定义在 wasm2c/wasm-rt-exceptions.h其 C 实现位于 wasm2c/wasm-rt-exceptions-impl.cvoid wasm_rt_load_exception(const char* tag, uint32_t size, const void* values); WASM_RT_NO_RETURN void wasm_rt_throw(void); WASM_RT_UNWIND_TARGET WASM_RT_UNWIND_TARGET* wasm_rt_get_unwind_target(void); void wasm_rt_set_unwind_target(WASM_RT_UNWIND_TARGET* target); uint32_t wasm_rt_exception_tag(void); uint32_t wasm_rt_exception_size(void); void* wasm_rt_exception(void); wasm_rt_try(target)各符号语义wasm_rt_load_exception将活动异常active exception设置为给定的 tag、大小和内容。wasm_rt_throw抛出活动异常。WASM_RT_UNWIND_TARGET异常被抛出并捕获时的 unwind target 类型。wasm_rt_get_unwind_target获取异常抛出时的当前 unwind targetwasm_rt_set_unwind_target设置之。三个访问函数wasm_rt_exception_tag、wasm_rt_exception_size、wasm_rt_exception分别返回活动异常的 tag、大小与内容。wasm_rt_try(target)宏将当前调用环境捕获为 unwind target 并存入target须为WASM_RT_UNWIND_TARGET类型。在 wasm2c/wasm-rt.h 中它被实现为WASM_RT_SETJMP_EXN(target)。导出符号Exported symbols最后fac.h定义了模块实例类型对fac而言基本为空以及模块提供的导出符号。本例中唯一的导出是fac函数typedef struct w2c_fac { char dummy_member; } w2c_fac; void wasm2c_fac_instantiate(w2c_fac*); void wasm2c_fac_free(w2c_fac*); wasm_rt_func_type_t wasm2c_fac_get_func_type(uint32_t param_count, uint32_t result_count, ...); /* export: fac */ u32 w2c_fac_fac(w2c_fac*, u32);wasm2c_fac_instantiate(w2c_fac*)创建模块实例在使用实例前必须先调用wasm2c_fac_free(w2c_fac*)释放实例。wasm2c_fac_get_func_type用于在运行时查询函数类型 ID。它是可变参数函数前两个参数给出参数个数与结果个数后续参数为上述wasm_rt_type_t枚举中的类型。callback示例wasm2c/examples/callback演示了如何借此在运行时向 WebAssembly 模块动态传入宿主函数。处理其他类型的导入与导出导出函数通过在头文件中声明带前缀的等价函数来处理。若模块导入函数wasm2c会在输出头文件中声明该函数由宿主函数负责定义实现。其他类型的导出全局变量、内存、表处理方式不同它们属于模块实例的一部分每个实例可以拥有各自的导出。对于这些情况wasm2c提供接受模块实例为参数、返回对应导出的函数。例如若fac导出一块内存(export mem (memory $mem))则wasm2c会在头文件中声明如下函数/* export: mem */ wasm_rt_memory_t* w2c_fac_mem(w2c_fac* instance);其定义形式为/* export: mem */ wasm_rt_memory_t* w2c_fac_mem(w2c_fac* instance) { return instance-w2c_mem; }宿主程序通过该访问器即可直接读写模块实例的线性内存这正是rot13示例中宿主与 Wasm 交换数据的基础。快速查看 fac.c 的内部实现fac.c的内容属于模块内部实现但了解其工作原理很有帮助。文件开头数百行定义了实现各种 WebAssembly 指令所需的宏如 fac.c 中的MEM_ADDR、TRAP(x)、FUNC_PROLOGUE/FUNC_EPILOGUE——后者在WASM_RT_STACK_DEPTH_COUNT模式下通过wasm_rt_call_stack_depth计数检测栈耗尽并TRAP(EXHAUSTION)。其后是各类初始化函数init、free、init_func_types、init_globals、init_memory、init_table、init_exports本例中它们大多为空因为模块没有使用全局变量、内存或表。最有趣的部分是fac函数本身的定义static u32 w2c_fac_fac_0(w2c_fac* instance, u32 var_p0) { FUNC_PROLOGUE; u32 var_i0, var_i1, var_i2; var_i0 var_p0; var_i1 0u; var_i0 var_i0 var_i1; if (var_i0) { var_i0 1u; } else { var_i0 var_p0; var_i1 var_p0; var_i2 1u; var_i1 - var_i2; var_i1 w2c_fac_fac_0(instance, var_i1); var_i0 * var_i1; } FUNC_EPILOGUE; return var_i0; }对照原始 WebAssembly 文本的扁平格式flat format可以看到输出与输入存在一一映射关系(func $fac (param $x i32) (result i32) local.get $x i32.const 0 i32.eq if (result i32) i32.const 1 else local.get $x local.get $x i32.const 1 i32.sub call 0 i32.mul end)它看起来与前面书写的阶乘函数不同是因为这里用了“扁平格式”而非“折叠格式”folded format。可以用wat-desugar在两种格式间转换验证$ wat-desugar fac-flat.wat --fold -o fac-folded.wat(module (func (;0;) (param i32) (result i32) (if (result i32) ;; label 1 (i32.eq (local.get 0) (i32.const 0)) (then (i32.const 1)) (else (i32.mul (local.get 0) (call 0 (i32.sub (local.get 0) (i32.const 1))))))) (export fac (func 0)) (type (;0;) (func (param i32) (result i32))))格式与变量/函数名虽不同但结构完全一致。创建模块的多个实例由于执行上下文信息如内存被封装在模块实例结构体中且该结构体的指针贯穿所有函数调用因此同一模块的多个实例可以在同一地址空间内并存互不干扰。以rot13示例wasm2c/examples/rot13的main函数变体为例通过声明两组上下文信息两个rot13实例即可在同一地址空间内实例化#include assert.h #include stdio.h #include stdlib.h #include rot13.h /* Define structure to hold the imports */ typedef struct w2c_host { wasm_rt_memory_t memory; char* input; } w2c_host; /* Accessor to access the memory member of the host */ wasm_rt_memory_t* w2c_host_mem(w2c_host* instance) { return instance-memory; } int main(int argc, char** argv) { /* Make sure there is at least one command-line argument. */ if (argc 2) { printf(Invalid argument. Expected %s WORD...\n, argv[0]); return 1; } /* Initialize the Wasm runtime. */ wasm_rt_init(); /* Create two host instances to store the memory and current string */ w2c_host host_1, host_2; wasm_rt_allocate_memory(host_1.memory, 1, 1, false, WASM_DEFAULT_PAGE_SIZE); wasm_rt_allocate_memory(host_2.memory, 1, 1, false, WASM_DEFAULT_PAGE_SIZE); /* Construct the rot13 module instances */ w2c_rot13 rot13_1, rot13_2; wasm2c_rot13_instantiate(rot13_1, host_1); wasm2c_rot13_instantiate(rot13_2, host_2); /* Call rot13 on the first two arguments. */ assert(argc 2); host_1.input argv[1]; w2c_rot13_rot13(rot13_1); host_2.input argv[2]; w2c_rot13_rot13(rot13_2); /* Free the rot13 instances. */ wasm2c_rot13_free(rot13_1); wasm2c_rot13_free(rot13_2); /* Free the Wasm runtime state. */ wasm_rt_free(); return 0; } /* Fill the wasm buffer with the input to be rot13d. * * params: * instance: An instance of the w2c_host structure * ptr: The wasm memory address of the buffer to fill data. * size: The size of the buffer in wasm memory. * result: * The number of bytes filled into the buffer. (Must be size). */ u32 w2c_host_fill_buf(w2c_host* instance, u32 ptr, u32 size) { for (size_t i 0; i size; i) { if (instance-input[i] 0) { return i; } instance-memory.data[ptr i] instance-input[i]; } return size; } /* Called when the wasm buffer has been rot13d. * * params: * w2c_host: An instance of the w2c_host structure * ptr: The wasm memory address of the buffer. * size: The size of the buffer in wasm memory. */ void w2c_host_buf_done(w2c_host* instance, u32 ptr, u32 size) { /* The output buffer is not necessarily null-terminated, so use the %*.s * printf format to limit the number of characters printed. */ printf(%s - %.*s\n, instance-input, (int)size, instance-memory.data[ptr]); }这段代码还示范了rot13模块如何通过导入宿主函数w2c_host_fill_buf把命令行输入写入 Wasm 内存与w2c_host_buf_done把转换结果读出打印与宿主交互其中宿主上下文w2c_host通过wasm2c_rot13_instantiate(rot13_1, host_1)传入模块实例充分体现了“实例即状态容器”的设计。小结至此从命令行基础用法、.wat → .wasm → .c的完整转换流程到生成头文件中w2c_*符号契约、wasm-rt.h的 trap/类型/内存/表数据结构、嵌入方必须实现的运行时 API、异常支持符号、Segue 优化以及多实例化模式本文已完整覆盖 wasm2c/README.md 的全部内容并对照 src/tools/wasm2c.cc、wasm2c/wasm-rt.h、wasm2c/wasm-rt-impl.c、wasm2c/wasm-rt-exceptions-impl.c 与 wasm2c/examples 系列示例做了源码级验证。进一步实践可参考 wasm2c/examples/fac、wasm2c/examples/rot13、wasm2c/examples/callback 与 wasm2c/examples/threads 四个完整示例工程以及test/wasm2c/目录下 260 余个测试用例。【免费下载链接】wabtThe WebAssembly Binary Toolkit项目地址: https://gitcode.com/GitHub_Trending/wa/wabt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考