WebAssembly: data セクションの最適化

# 概要

 WebAssembly 逆アセンブル[^wasm-dis] と Rust 製の WebAssembly オプティマイザの [twiggy](https://github.com/rustwasm/twiggy) を駆使して、WebAssembly ファイル中の data セクションのサイズ削減を図る

[^wasm-dis]: .wasm を .wat に変換すること

# 対象読者

- WebAssembly を極限まで小さくするための、いかなる苦労を厭わない人
  - この記事中に、ツールを使えば簡単に WebAssembly を極限まで小さくできる、魔法のような方法が紹介されていると期待していた人はブラウザバック推奨
- WebAssembly Text Format にアレルギーのない人

# 使用ツール

## [WebAssembly for VSCode](https://marketplace.visualstudio.com/items?itemName=dtsvet.vscode-wasm)

Visual Studio Code 上で、次の機能を提供する拡張機能

- WebAssembly を WebAssembly Text Format に変換する
- WebAssembly Text Format の色付け

### インストール方法

Visual Studio Code の拡張機能ペインから、**WebAssembly** と検索することでインストール可能

![スクリーンショット 2021-10-28 14.08.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158514/0f23664c-9d39-382d-36a5-67ad985be08f.png)

## [twiggy](https://github.com/rustwasm/twiggy)

Rust 製の WebAssembly 最適化のユーティリティツール。次に紹介するようなコマンドを備えている

- `twiggy top`
  - どの部分がどのくらいのサイズを食っているかを一覧表示する
- `twiggy garbage`
  - 不要と思われる部分を一覧表示する

### インストール方法

[Rust](https://www.rust-lang.org/ja/) をインストールした上で、次のコマンドを実行する

```sh
cargo install twiggy
```

# WebAssembly のデータセクションの前提知識

他プラットフォームの実行形式では、静的格納領域に .bss セクション(初期化が不要な領域)と .data セクション(初期化が必要な領域)の区別がある
この .bss セクションに格納されるべきものが増えても、実行形式のファイルサイズには影響を与えない

しかし、WebAssembly には .bss セクションに該当するセクションは存在しない
そのため、.bss セクションに格納されるべきものも、データセクションに格納されてしまい、WebAssembly のファイルサイズを大きくしてしまう (特にこれはデバッグビルドで顕著である)

ただ、WebAssembly の起動時に WebAssembly の線形メモリがゼロ初期化されていることが期待できる (むしろゼロ初期化されてなければ大問題である)
それを利用して、次のように複数のデータセクションに分けることで、データセクションのトータルサイズが小さくすることができる (これは、リリースビルドで自動的に有効になる)

```wat
  (data $d0 (i32.const 1026) "\80?")
  (data $d1 (i32.const 1046) "\80?")
  (data $d2 (i32.const 1066) "\80?")
```

# 不要そうなデータセクションの ID を検索する

次のコマンドを実行する

```sh
twiggy garbage <対象の.wasm ファイル> --show-data-segments > DataDump.txt
```

すると、次のような出力が得られる

```txt:DataDump.txt
 Bytes   │ Size % │ Garbage Item
─────────┼────────┼───────────────────────────────
 2357457 ┊ 15.57% ┊ custom section '.debug_info'
 1519400 ┊ 10.03% ┊ custom section '.debug_str'
 1116168 ┊  7.37% ┊ custom section '.debug_loc'
 1098687 ┊  7.25% ┊ custom section '.debug_line'
  160386 ┊  1.06% ┊ custom section '.debug_abbrev'
  119256 ┊  0.79% ┊ custom section '.debug_ranges'
   56690 ┊  0.37% ┊ data[3430]
   44189 ┊  0.29% ┊ data[1052]
   31550 ┊  0.21% ┊ data[312]
   29917 ┊  0.20% ┊ data[1181]
  911216 ┊  6.02% ┊ ... and 3564 more
 7444916 ┊ 49.16% ┊ Σ [3574 Total Rows]
```

ここからは、data[3430] が不要なデータかどうかを検証していく

# 不要そうなデータセクションのコードベースを探す

Visual Studio Code のエクスプローラペインで、対象の WebAssembly ファイルを右クリックし、**Show WebAssembly** をクリックする

![スクリーンショット 2021-10-28 14.39.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158514/a340fb7e-ecba-7476-40c9-ab3dfb45ec63.png)

すると、次のように WebAssembly Text Format が表示される

![スクリーンショット 2021-10-28 14.42.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158514/845d58ec-096e-4993-5abd-9c56af7b093d.png)

この WebAssembly Text Format のうち、data[3430] を探しに行く
WebAssembly Text Format では、データセクション ID は `$d<セクションID>` という形式をとっているので、**d3430** と検索すると便利である。

![スクリーンショット 2021-10-28 14.44.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158514/6c084bd1-2331-ddc0-ab16-389a987b9ed1.png)

```wat
(data $d3428 (i32.const 1290470) "\f0\bf\00\00\
(data $d3429 (i32.const 1290926) "\e0?\00\00\00
(data $d3430 (i32.const 1291006) "\e0?\00\00\00
(data $d3431 (i32.const 1347696) ">\b4\e43\09\9
(data $d3432 (i32.const 1348764) "N\0e\00\00O\0
```

すると、静的領域のアドレス値が割り出せる (ここでは **1291006**) ので、今度はこのアドレス値で検索していく
ドンピシャなアドレス値が検索にかかることは稀なので、**12910** とすこし曖昧に検索することとする

今回は次の部分が検索に引っかかったので、このコード断片が含まれる関数名まで遡ると、`_vp_couple_quantize_normalize` であった。

```wat
   i32.const 2
   i32.shl
   i32.const 1291072
   i32.add
   f32.load
   f32.store
```

emscripten のライブラリキャッシュ中で `_vp_couple_quantize_normalize` と検索すると、これは libvorbis.a の [psy.c](https://github.com/emscripten-ports/Vorbis/blob/master/lib/psy.c#L1007) に含まれる関数であることが判明した

関数中に `FLOOR1_fromdB_LOOKUP` という、サイズが 1KB のグローバル変数があり、これがデータセクションに載っていることがわかった

ただ、今回の調査では libvorbis.a を使う限り、これは削減不可能なものであることが判明しただけであった

# あとがき

今回のケースでは、データセクションの削減に至らなかった

しかし、同様の調査方法で libjpeg-turbo.a のデータセクションの削減に挑んだとき、[jpeg_nbits_table.h](https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/jpeg_nbits_table.h) の内容を、WebAssembly のオプコード (i32|i64.clz) を使うことで、削減可能と判明し、結果 100KB 程度の削減に成功できたケースも存在する

地味で労力のかかる方法ではあるが、現状これに勝る方法がない限り、この方法に頼らざるを得ない状況は変わらないであろう (もっとも、データアドレスとコードベースのマッピングは DWARF に含まれているはずなので、それを使いこなせるようになれば少しは楽になるであろうか)

また、C++ の型情報もデータセクションサイズに大きいインパクトを与えうる。今一度、WebAssembly をビルドするときに、不要なコードを含めてしまっていないかを十分に検討してほしい (そのために、コードカバレッジツールを emscripten で使えるようにするための方法を模索している)