☰
Dart2Wasm 产物检查指南:用 `--print-wasm` 与 `wami` 深入解析生成的 WebAssembly 代码
2026/9/25 14:45:25 网站建设 项目流程
  • 编程语言
  • 编译器
  • 语言运行时
  • 标准库
  • 开发工具

【免费下载链接】sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.

项目地址:https://gitcode.com/gh_mirrors/sdk1/sdk
点击查看免费下载

导读

将 Dart 代码编译为 WebAssembly(Wasm)后,产出的.wasm文件是二进制格式,直接查看几乎不可能。本文以 Dart SDK 中 dart2wasm 编译器的官方文档 pkg/dart2wasm/docs/inspecting_wasm.md 为核心骨架,系统讲解两条检查 Wasm 产物的完整路径:一是直接使用 dart2wasm 编译器自带的--print-wasm标志,在编译过程中打印每个函数的 WAT 指令流;二是使用 V8 仓库自带的wami工具对编译产物做节(section)统计与全量 WAT 反汇编。读完本文,你将掌握如何在编译期输出中间指令、如何用wami生成.stats与.wat文件,以及如何结合仓库源码理解这些输出的来源,从而高效定位大小、结构与生成质量问题。

为什么需要检查 Wasm 产物

dart2wasm 编译器的目标是"把 Dart 编译为符合规范的 Wasm 模块",但最终产物(.wasm)是二进制格式。要回答以下问题,就必须对产物进行"透视":

  • 体积从哪来:哪些函数、全局变量、常量池占用了大量字节;
  • 结构是否合理:Type / Import / Function / Export / Code 等节的规模分布,是否出现异常膨胀;
  • 代码生成是否符合预期:内联是否生效、动态分派是否走了 dispatch table、类型检查是否被省略等;
  • 调试与回归分析:改动编译器后,需要对比某个函数生成的指令前后差异。

为此,Dart SDK 的 dart2wasm 子项目(pkg/dart2wasm)在官方文档中给出了两套互补的工具链:编译器内建打印(编译期输出)与外部反汇编(产物级分析)。

方案一:使用编译器自身的--print-wasm

基本用法

dart2wasm 编译器内置了--print-wasm标志。启用后,编译器在生成每个函数体时会同时把对应的 WAT(WebAssembly Text format)指令输出到控制台。官方文档给出的示例命令如下:

pkg/dart2Wasm/tool/compile_benchmark --compiler-asserts --print-wasm app.dart app.wasm

其中:

  • --compiler-asserts:让编译器在带断言(assert)的模式下运行,便于调试编译器本身(对应仓库脚本中的SNAPSHOT_NAME="dart2wasm_asserts",见 pkg/dart2wasm/tool/compile_benchmark);
  • --print-wasm:核心开关,要求编译器把生成的 Wasm 指令打印出来;
  • app.dart/app.wasm:输入源码与输出文件。

需要注意的是,示例命令中使用的是pkg/dart2Wasm/tool/compile_benchmark,这是 dart2wasm 目录下的一个基准测试编译脚本(脚本内实际路径为pkg/dart2wasm/tool/compile_benchmark,注意大小写)。该脚本假定 Dart 仓库的标准目录结构,会从out/<配置>/下定位dart、dartaotruntime、dart2wasm_asserts.snapshot以及--libraries-spec=$SDK_DIR/sdk/lib/libraries.json等构建产物(见脚本中BIN_DIR="$OUT_DIR/$DART_CONFIGURATION"与LIBRARIES_JSON_ARG的定义)。它会把除特殊识别参数外的所有参数原样透传给 dart2wasm,因此--print-wasm最终由 dart2wasm 命令行解析。

--print-wasm在源码中的定义与实现

--print-wasm标志在编译器命令行参数表中注册,位于 pkg/dart2wasm/lib/dart2wasm.dart:

Flag( "print-wasm", (o, value) => o.translatorOptions.printWasm = value, defaultsTo: _d.translatorOptions.printWasm, ),

它最终落到TranslatorOptions.printWasm字段上,该字段默认值为false(见 pkg/dart2wasm/lib/translator.dart 的bool printWasm = false;)。

在翻译阶段,printWasm会被传入编译任务并实际控制输出。核心输出逻辑集中在 pkg/dart2wasm/lib/translator.dart 的CompilationTask.run:

void run(Translator translator, bool printKernel, bool printWasm) { assert(function != function.moduleBuilder.startFunctionIfCreated); if (printWasm) { print("#${function.name} (synthetic)"); print(function.type); } _codeGenerator.generate(function.body, function.locals.toList(), null); if (printWasm) { print(function.body.trace); } ... }

也就是说,--print-wasm的输出内容包含三部分:

  1. 函数头:以#函数名形式打印(合成函数会标记(synthetic));
  2. 函数签名:函数类型(参数与返回值类型);
  3. 指令流:function.body.trace,即该函数生成的完整指令序列(WAT 风格)。

对于普通 Dart 成员函数,AstCompilationTask.run还会额外打印导出名与类型信息(见 pkg/dart2wasm/lib/translator.dart):

if (printKernel || printWasm) { final (:name, :exportName) = _getNames(translator); String header = "#${function.name}: $name"; if (exportName != null) { header = "$header (exported as $exportName)"; } print(header); print(function.type); ... }

此外,全局变量的初始化器(Global #...与initializer.trace)以及init函数(模块启动函数)也会在printWasm开启时被打印,见 pkg/dart2wasm/lib/translator.dart 与_printFunction(pkg/dart2wasm/lib/translator.dart)。

--print-wasm适合在编译同一时刻查看代码生成结果,无需额外工具;它打印的是 dart2wasm 内部wasm_builder的指令追踪(trace),与最终模块中序列化出来的指令一致。不过当输出文件被wasm-opt(Binaryen)二次优化后,产物与打印内容会有差异,这是使用该方案时需要注意的边界(脚本中-O0会关闭 Binaryen 阶段,见 pkg/dart2wasm/tool/compile_benchmark 的RUN_BINARYEN=0)。

打印内核 Kernel 代码作为对照

--print-wasm常常与--print-kernel搭配使用:前者打印生成后的指令,后者打印前端(CFE)解析出的 Kernel IR 节点。二者的对照可以在 pkg/dart2wasm/lib/translator.dart 中看到——printKernel分支会打印构造器字段初始化器、成员函数体等 Kernel 结构,而printWasm分支打印指令追踪;同时开启时,两条输出交替出现,非常适合追踪"某段 Dart 源码 -> Kernel 节点 -> Wasm 指令"的完整链路。

方案二:使用 V8 的wami工具检查 Wasm 文件

wami是什么

wami(wasm inspect 的缩写)是 V8 代码仓库内置的一个 Wasm 文件检查工具,其二进制位于 V8 构建目录out/x64.release/wami。它与 dart2wasm 编译器相互独立,直接以.wasm产物文件为输入,因此也适用于任何符合规范的 Wasm 模块。

获取wami:Checkout 并构建 V8

dart2wasm 文档目录中提供了配套的 V8 构建指南 pkg/dart2wasm/docs/v8.md,关键步骤(假设depot_tools已安装且在PATH中):

% mkdir v8-gclient && cd v8-gclient v8-gclient % fetch v8 && cd v8 v8-gclient/v8 % tools/dev/gm.py x64.release d8 wami

说明:

  • fetch v8获取 V8 源码(官方源码与构建文档另见 V8 官网,本仓库不包含);
  • tools/dev/gm.py x64.release d8 wami会产出out/x64.release/wami二进制;
  • 若因 GOMA/RBE 相关环境变量导致构建失败,先清空所有 goma 相关环境变量再重试;
  • 如需使用 profiling 支持,可在out/x64.release/args.gn中加入enable_profiling = true后重新构建。

配置 PATH 或 alias

为方便使用,文档建议两种方式:

# 方式一:加入 PATH export PATH=<path-to-v8>/out/x64.release:$PATH # 方式二:在 shell 配置文件中添加 alias alias wami=<path-to-v8>/out/x64.release/wami

之后便可在任意目录直接调用wami。

统计 Wasm 文件的各节信息

Wasm 二进制由若干节(section)组成:类型(Type)、导入(Import)、函数(Function)、表(Table)、内存(Memory)、全局(Global)、导出(Export)、代码(Code)、数据(Data)等。wami的--section-stats可以统计各节的规模:

% wami --section-stats -o app.stats app.wasm

该命令把统计结果写入app.stats。输出的节统计信息适合做体积归因:例如当某个.wasm明显偏大时,先看是Code节(函数体指令)膨胀,还是Data/Global/Type节异常,再据此深入对应函数。

反汇编:导出完整 WAT 文本

wami可以把整个 Wasm 文件反汇编为人类可读的 WAT 文本,支持两种模式:

% wami --offsets --full-wat -o app.wat app.wasm % wami --full-wat -o app.wat app.wasm
  • --full-wat:输出完整 WAT 文本(函数体、类型、导入导出、全局、数据段等);
  • --offsets:在每个指令后附加其在二进制文件中的字节偏移,用于精确定位问题指令的物理位置,也便于与十六进制工具对照;
  • -o app.wat:将结果写入文件而非终端,方便后续 grep、diff 与代码评审。

由此生成的app.wat是纯文本,可以用任意文本工具进一步检索,例如:

# 查找某个函数是否生成、被内联 grep -n "main" app.wat # 对比两次编译产物的差异 diff <(wami --full-wat app1.wasm) <(wami --full-wat app2.wasm)

仓库内的轻量替代:wasm2wat.dart

除了外部wami,dart2wasm 仓库自身还提供了一款 Dart 编写的反汇编工具 pkg/dart2wasm/bin/wasm2wat.dart,其定位与wami类似(源码注释中直接以wami equivalent is false说明默认值对齐),但功能更贴近 Dart 生态,支持:

  • -f / --function-name-filter:只打印函数名匹配正则的函数体;
  • -t / --type-name-filter、-g / --global-name-filter:按名称过滤类型与全局变量;
  • -s / --sort:按名称排序打印函数、类型等;
  • -w / --write:把结果写到<input.wasm>.wat;
  • -p / --print-source-positions:结合源码映射(source map)把指令对应的 Dart 源码位置以注释形式打印出来;
  • -o / --output:指定输出文件(多输入时需配合--write)。

其核心调用链为Module.deserialize+printAsWat(见 pkg/dart2wasm/bin/wasm2wat.dart),读取的是与--print-wasm相同的wasm_builder序列化格式。对于只在 Dart SDK 仓库内工作、不想额外构建 V8 的场景,这是一个更顺手的备选。

两套方案的对比与选型建议

维度--print-wasmwami/wasm2wat.dart
输入Dart 源码(编译期).wasm产物(编译后)
输出控制台打印,含函数头、签名、指令 trace、全局初始化器节统计(.stats)或完整 WAT(.wat)
依赖仅需 dart2wasm 构建产物需构建 V8 的wami,或用仓库内wasm2wat.dart
典型场景调试代码生成、跟踪源码到指令的映射体积归因、全量反汇编、产物 diff
与 wasm-opt 的关系打印的是 Binaryen 优化前的指令直接作用于最终产物,包含优化后的结果

实际工作流中两者常配合使用:先用--print-wasm快速确认某个函数的生成逻辑,再用wami --section-stats定位体积热点、用--full-wat对优化后的最终产物做整体检查。

小结

本文围绕 pkg/dart2wasm/docs/inspecting_wasm.md 展开:编译器侧的--print-wasm让你在编译期看到每个函数(含合成函数、全局初始化器与init启动函数)的指令 trace,其实现位于 pkg/dart2wasm/lib/translator.dart 的CompilationTask.run与_printFunction;产物侧的wami(配合 pkg/dart2wasm/docs/v8.md 构建)则提供--section-stats节统计与--full-wat/--offsets全量反汇编能力,仓库内还提供了等价替代工具 pkg/dart2wasm/bin/wasm2wat.dart。掌握这两条检查路径后,你既可以逐函数审视代码生成质量,也可以对最终.wasm文件做系统的体积与结构审计。

  • 编程语言
  • 编译器
  • 语言运行时
  • 标准库
  • 开发工具

【免费下载链接】sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.

项目地址:https://gitcode.com/gh_mirrors/sdk1/sdk
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询