mozcの変換コードを理解してみる (on Windows)

# はじめに

Rinna から提供されている gpt モデルを使って遊んでみています。
- [Rinna Gpt2 モデルを onnx データにした](https://qiita.com/shinjimorimitsu/items/b61aa693f8e2988c8590)
- [IMEのリランカでGPTが使えるか試してみる](https://qiita.com/shinjimorimitsu/items/f4b5ecccf88a41263fe9)
というところで、実装するのに mozc を読むことにしました。

# mozc for Windows をビルドする

以下の環境で、[ドキュメント通り](https://github.com/google/mozc/blob/master/docs/build_mozc_in_windows.md)の手続きで問題なくビルド終了しました。
- Windows 11 (10.0.22631)
- VS2022 Enterprise

# TTD でトレースすると理解が深まる

仕事ではよくあることですが、既存の大きなコードを修正なり拡張しなければならないが、周りに概要すら教えてくれる人もなく、取っ掛かりすらつかめないことがあったりしますね。そういう場合、私はstaticにソースを読むのがとても苦手で、どちらかというとデバッガでトレースしながら必要な場所を読む方が何倍も早く理解できます。
そして、更に、それを手助けしてくれるのが、[TTD (Time Travel Debugging)](https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/time-travel-debugging-overview) です。説明を引用すると「Time Travel Debugging は、プロセスの実行時にプロセスのトレースをキャプチャし、後で前後に再生できるツールです。」　これで知りたい挙動のプロセスのトレースをキャプチャしておき、あとから、デバッガでコードポインタを行ったり来たりさせて、知りたい場所の挙動を理解するのが、最近のお気に入りです。

詳細は、[本家](https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/time-travel-debugging-overview)におまかせしますが、
`g`: go, `g-`: go abck, `p`: step, `p-`: step back, `t`: trace-in, `t-`: trace-back-in (?) など、一度見たかったところを通り過ぎたとしても、すぐに戻してやり直したり、後ろ向きにステップ捺せたり、まあパワフルにできます。

## インストール

TTD のキャプチャデータを開くには windbg が必要なので、
- [TTD https://aka.ms/ttd/download](https://aka.ms/ttd/download)をターゲットVMに
- [windbg](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/) を開発PCにインストールします。

## アプリプロセスをキャプチャしてみる

ターゲットVMにて、
1. notepad を起動して、プロセスIDを調べる
1. `C:\Users\User\AppData\Local\Microsoft\WindowsApps\ttd -out c:\traces -attach {PID}` として、キャプチャを始める
1. IME ON/ローマ字入力/変換 など一連の動作を試して、トレースを終了
1. c:\traces に `notepad01.run` というファイルをができるので、これをホストPCにコピーする
1. windbg に mozc のソースパスとシンボルパスを設定する (e.g. src:`D:\Git\mozc\src`, symbol:`D:\Git\mozc\src\out_win\Release{,_x64,ReleaseDynamic_x64`)
1. windbg => Open trace file でキャプチャファイルを開く

TIP のキーイベントハンドラ関数 OnKeyDown にブレイクポイントを貼り (`bm mozc_tip64!*OnKeyDown`)、その後トレースしていくと、message pipe の書き込みで終わります。

```
[0x3]   mozc_tip64!mozc::`anonymous namespace'::SendIpcMessage+0x75   0xa51e9fe840   0x7166259c   
[0x4]   mozc_tip64!mozc::IPCClient::Call+0x5c   0xa51e9fe8d0   0x71580741   
...

[0xa]   mozc_tip64!mozc::win32::tsf::`anonymous namespace'::OnKey+0x8ee   0xa51e9fefe0   0x71529174   
[0xb]   mozc_tip64!mozc::win32::tsf::TipKeyeventHandler::OnKeyDown+0x23   0xa51e9ff500   0x7ffef9c4686b   
[0xc]   mozc_tip64!mozc::win32::tsf::`anonymous namespace'::TipTextServiceImpl::OnKeyDown+0x44   0xa51e9ff500   0x7ffef9c4686b
```

## サーバープロセスをキャプチャしてみる

さて、そこで、再度 mozc_server.exe のプロセスIDから、プロセストレースをキャプチャして見ました
`bm mozc_server!mozc::SessionHandler::EvalCommand` あたりがクライアントリクエスト最初のハンドラのようなので、そこにブレイクポイントを仕掛けて、変換キー「SPACE」の挙動を調べてみます。

```
[0x0]   mozc_server!mozc::ImmutableConverterImpl::ConvertForRequest+0x16b   0x3398ff6c0   0x7ff652266875   
[0x1]   mozc_server!mozc::ConverterImpl::Convert+0xd1   0x3398ff770   0x7ff652121398   
[0x2]   mozc_server!mozc::ConverterImpl::StartConversionForRequest+0x1d5   0x3398ff770   0x7ff652121398   
...
[0x8]   mozc_server!mozc::SessionHandler::EvalCommand+0x163   0x3398ffa90   0x7ff6520f36c3   
[0x9]   mozc_server!mozc::SessionServer::Process+0xc3   0x3398ffb70   0x7ff652103dd5   
[0xa]   mozc_server!mozc::IPCServer::Loop+0x4a5   0x3398ffc00   0x7ff6520b6d2d   
[0xb]   mozc_server!mozc::server::MozcServer::Run+0x17d   0x3398ffda0   0x7ff6520b6189   
[0xc]   mozc_server!WinMainToMain+0xc5   0x3398ffe50   0x7ff6523013ee   
[0xd]   mozc_server!WinMain+0xf9   0x3398ffe50   0x7ff6523013ee   
```

そいう感じで変換コードに届くようです。

# そして変換コードを理解してみる

簡単に書くと、

- `ImmutableConverterImpl::MakeLattice` で lattice を作っている。このときはコスト計算はやってなさそう(ビームなど)
- `ImmutableConverterImpl::Viterbi` で上記で作った lattice を評価している。Viterbi　の Best Path がそのまま変換結果として使っているように見える
- NBest リランキングなどしていない。(NBestGenerator というコードがあるが、次候補リスト用だと思われる)

なので、Viterbi が forward で累積スコアを保存しているので backward A* なぞ、書くことができるか思案中。

# Mozc を応援するいくつかの方法

作業中、[Mozc を応援するいくつかの方法](https://zenn.dev/komatsuh/articles/91def2bc633a8d) というリンクを見つけました。是非、皆さんやってみましょう。