# 0004 0.2.0–0.4.0 路线：size、opt、compose、addr2line

- Status: Proposed
- Date: 2026-11-05
- Supersedes: —

## Context

0.1.0 已交付 core/component 的 validate/print/parse/dump/strip/metadata 与
`component wit`，并验证了 `moonx` 预编译资产链路。剩余缺口分两类：

1. **已交付面的正确性缺口**：`dump`/`metadata` 拒绝 component；`component wit`
   还没有带真实 WIT 的组件固件。
2. **新能力**：`size`、`opt`、`compose`、`addr2line`。其中 `size` 是
   `moonsize` native-only 留下的 moonx 缺口，`opt` 可以复用工具链自带的
   binaryen，`compose` 可以复用 `mizchi/mwac`，`addr2line` 需要先验证
   `dwarfscope` 的依赖与 wasm 兼容性。

## Decision

### 顺序

| 版本 | 内容 | 理由 |
|---|---|---|
| 0.2.0 ✅ | component 感知的 `dump`/`metadata`；`size` v1（section + function + call graph） | 无新依赖；先补齐已有命令的正确性，再交付差异化最大的 size |
| 0.3.0 ✅ | `opt`（包装 `moon-wasm-opt`）；`size --diff`/`--budget` | opt 依赖宿主 spawn，需要单独的失败路径与提示；diff/budget 建立在 size v1 上 |
| 0.4.0 | `compose`（WAC）；`addr2line` | 两者都引入新的上游依赖，放到最后，失败不影响前三个版本 |

`addr2line` 开工前先做 spike：`wuhaiting321/dwarfscope` 没有根包，需要导入
`src/dwarf_line` 等子包，且依赖 `moonbitlang/x@0.4.48`。若与项目的
`x@0.5.x` 冲突或 wasm 目标不可用，则本版本只交付 `compose`，addr2line 另立 ADR。

### 0.2.0：component 结构 + size

**component `dump`/`metadata`**

- 组件顶层 section id：0 custom、1 core module、2 core instance、3 core type、
  4 nested component、5 instance、6 alias、7 type、8 canon、9 start、10 import、
  11 export。按此表输出，不再复用 core 的 1..13 标签。
- `dump --instructions` 对组件只输出 section 表，不逐个展开嵌套 core module；
  需要展开时用 `print` 的多文档 WAT。
- `metadata` 读取组件顶层的 custom section；嵌套 core module 的 name/producers
  在输出中标记 `core[0]` 前缀。

**`size` v1**

```text
wsm size FILE [--top N] [--json]
```

- 总大小、header、各 section 的 `payload`/`total`/百分比。
- 函数大小：解析 code section 的每个 body 长度；函数索引加上 imported 数量；
  名字来自 name section subsection 1。
- 调用图：从 `Instruction::Call`/`ReturnCall` 建边；`CallIndirect`/`CallRef`
  记录为 `indirect` 但不猜测目标。
- 排序输出 top-N，`--json` 字段固定：`file`、`total`、`sections[]`、
  `functions[]`、`call_graph`、`indirect_calls`。

验收：

- 各 section `total` 之和 + 8 字节 header 等于文件大小。
- 函数 body 大小之和 + code section 头部 ≤ code section payload。
- MoonBit `wasm`/`wasm-gc` release 产物上，导出的入口函数出现在 top-N。
- `--json` 对同一输入稳定；不打印未排序的 Map 迭代结果。

### 0.3.0：opt 与 size 门禁

**`opt`**

```text
wsm opt FILE [-O1|-O2|-O3|-O4|-Os|-Oz] [--passes P1,P2] [-o OUT]
```

- 默认追加 `--all-features`：MoonBit 的 wasm-gc 产物含 GC/SIMD，binaryen 不加
  该开关会直接拒绝（已实测）。
- 查找顺序：`--wasm-opt PATH` → `$MOON_HOME/bin/moon-wasm-opt[.exe]` →
  `PATH` 中的 `moon-wasm-opt`。
- stdin 输入写到 `@fs.tmpdir` 的临时文件；stdout 输出也经临时文件转回。
- 子进程失败时透传 binaryen stderr，退出码 1；找不到/无法 spawn 退出码 2，
  并提示 `moon install` 或 `--wasm-opt`。
- 优化后先用本项目的 `validate` 再写出，避免产出损坏文件。

**`size --diff` / `--budget`**

```text
wsm size NEW.wasm --diff OLD.wasm [--json]
wsm size FILE --budget 200KB
```

- `--diff` 输出函数级 delta：`added`/`removed`/`changed`，按绝对值排序。
- `--budget` 超限退出码 1，供 CI 使用；单位支持 `B/KB/MB`（1024 进制）。
- 这两项不改变 `size` v1 的默认输出。

### 0.4.0：compose 与 addr2line

**`compose`（阻塞，未发布）**

本地已实现并验证：`mizchi/mwac@0.2.2` 可编到 wasm，102B 组件组合成 139B，
`--dep` 覆盖与 `--dce` 都可用。但发布到 Mooncakes 时构建失败：

```text
Type Array[RTypeDef] has no method to_json
  at .mooncakes/mizchi/wit/src/resolve.mbt:630
```

registry 的构建工具链与本地 `0.1.20261029` 版本不同，`mizchi/wit@0.3.3`
在它上面编不过；0.4.0/0.4.1 因此只有 `build_status: failed`，没有预编译资产。
在 `mizchi/wit` 兼容之前，`compose` 不进入发布版本，避免用户拿到无法运行的
版本号。重新引入的条件：registry 构建日志不再出现该错误，且
`moonx <module> compose --help` 能实际跑通。

**`addr2line` → `symbolize`**

spike 结论：`dwarfscope` 的子包可以编译到 wasm，且与项目的 `x@0.5.x` 不冲突；
但 MoonBit 的 wasm 产物**不包含 DWARF**。用 `-g`、`--debug --no-strip`、
`--debug --strip` 三种组合实测，custom section 始终只有 `name` 与 `producers`。
因此 `addr2line` 没有数据源，按 ADR 的预设改为交付：

```text
wsm symbolize FILE ADDRESS [--json]
```

- 接受 `wasm-function[34]:0xd8d` 或十六进制绝对偏移
- `0xd8d` 是**模块内绝对字节偏移**，不是函数内偏移。用两个真实崩溃点验证：
  `wasm-function[34]:0xd8d` 落在函数 34 的 body `[3439, 3479)` 内，
  `wasm-function[44]:0x13f8` 落在函数 44 的 `[5107, 5122)` 内。
- 名称来自 `name` section subsection 1；release 产物没有函数名时输出 `(unnamed)`
- 如果将来 MoonBit 开始生成 DWARF，再引入 `addr2line`，`symbolize` 保留作为快速路径

### 版本与兼容

- 0.2.0 只新增命令与可加字段，不改变 0.1.0 的输出与退出码。
- `size --json` 从 0.2.0 起视为稳定接口；新增字段必须可选。
- 每个版本发布前执行：`moon test`、`tests/smoke.sh`、`moon build` 双目标、
  `moon publish --dry-run`，发布后用 `moonx <module>@<version> --version` 验证资产。

## Consequences

- `size` 完全自研，但只依赖 `wasm_core` 已有的指令模型与 raw 层，不引入新包。
- `opt` 不维护优化 pass，但引入对宿主进程的依赖；沙箱环境需要降级提示。
- `compose` 让依赖树多出 `mizchi/wit` 与 `async`，预计二进制增加 0.3–0.5 MB；
  若超过 3.5 MB，再评估拆成 `wsm-compose` 子模块。
- `addr2line` 因 MoonBit wasm 无 DWARF 被 `symbolize` 取代。
- `compose` 的 `mizchi/mwac` 依赖链不能编译到 native，模块因此只声明 wasm target；
  这是发布 0.4.0 时 registry 构建失败暴露的约束。

## Verification

0.2.0 的 size 用两类固件交叉验证：

- 手工构造的最小模块：section/body 大小可人工核对。
- MoonBit release 产物：与 `ls -l`、`wasm-opt --metrics`（若可用）对比总量。

`opt` 的验收必须包含 MoonBit wasm-gc 产物，因为它是 `--all-features` 的直接
动机；只测手写 WAT 不足以证明。0.3.0 的实测记录：

- Linux：`moon-wasm-opt` 走 PATH；139088 B -> 95389 B（`-Oz`），产物重新验证通过。
- Windows：从 Windows 工具链解出 `moonrun.exe` 与 `moon-wasm-opt.exe`，在 WSL
  interop 下直接运行 `wasm-tools.wasm`，`opt -Oz` 同样通过；`@fs.tmpdir` 返回
  `C:\Users\...\AppData\Local\Temp\...`，临时文件可读写与清理。
- 显式 `--wasm-opt PATH` 是权威路径：探测失败直接退出 2，不回退到其它 binaryen。

## Reconsideration

如果 `mizchi/mwac` 在 0.4.0 前修复发布流程并暴露更小的 composer-only 包，
`compose` 可以提前到 0.3.0；如果 `moon-wasm-opt` 在某个平台不随工具链分发，
`opt` 需要改为可选插件而不是内置命令。
