| name | rebrgen-new-feature |
| description | rebrgen のコードジェネレーターに新機能を追加する時の方法と注意点。コードジェネレーターの構造の理解、共通部分と言語固有部分の整理、EBM構造の変化への対応などを扱う。 |
用語
ebm2lang - ebm2c,ebm2go,ebm2zigなどのEBMから各言語へのコードジェネレーターの総称。ebm[cg|ip]/ディレクトリ内に実装されている。
構成要素
-
extended_binary_module.bgn: EBMの構造を定義するファイル。EBM IRの構造を変更する場合はこのファイルを編集する必要がある。
-
ebmcodegen/stub/: コードジェネレーターの共通のユーティリティ関数等を置くディレクトリ。ここにはEBM IRの構造に依存しておりかつ言語固有の処理を必要としないロジックを実装することが望ましい。
-
ebmcodegen/default_codegen_visitor/visitor/: コードジェネレーターの共通のvisitorの実装を置くディレクトリ。ここにはEBM IRの各ノードに対応するデフォルトの処理が置かれている。
- ebmcodegen/default_codegen_visitor/visitor/Visitor.hpp: デフォルトの生成のカスタマイズ用の設定値やフックの定義が置かれているファイル。ctx.config().*_visitorやctx.config().*_custom, ctx.config().*_wrapperの定義はここにある。また単純に設定値で変換可能なパターンのためのデフォルト設定なども置いてある。
-
ebmgen: IRコンパイラのディレクトリである。EBM IRの物理レイアウトを編集するときはextended_binary_module.bgnを編集し、update_ebm.pyを使って更新する必要がありさらにそれに対応するロジックも追加していく必要がある。またEBM IRの論理構造を編集するときはebmgen/transform/内に処理を追加するかあるいはebmgen/convert/内のASTからEBM IRへの変換処理を編集する必要がある。
-
ebmcg/ebm2lang/visitor/: 各言語固有のコードジェネレーターの実装を置くディレクトリ。ebm2c, ebm2go, ebm2zigなどの各言語固有のコードジェネレーターの実装はここに置いてある。EBM IRの構造を変えない追加であっても、特定のノードを特定のパターンで変換するような追加の場合はここに処理を追加する必要がある。
ebmcgとebmipについて
ebmcg/ は ebm code generatorの略で、default codegen visitor を継承する generator を置く場所である。Result型にCodeWriterを含み、EBM IRから各言語のソースコードを組み立てる典型的なコード生成用途(C, Go, Rust, Python 等)に向いている。
ebmip/ は略称の由来こそ ebm interpreter (最初の住人 ebm2rmw が interpreter だった)だが、実体は「default codegen visitor (CodeWriter 中心) が不適な generator 全般の受け皿」 である。現在の住人は ebm2rmw(interpreter)と ebm2json(シリアライズ出力)であり、将来 ebm2ascii(可視化)等もここに入る予定。詳細は ADR 0005 を参照。
基本的にこれらの中のディレクトリ構造などはscript/ebmcodegen.pyを使って生成されているものである。手動で編集していいのは
ebm[cg|ip]/ebm2lang/visitor/ディレクトリ内とebm[cg|ip]/ebm2lang/内のunictest.pyやunictest_runner.jsonなどの設定のみである。特にebm[cg|ip]/ebm2lang/visitor/ディレクトリ内はコードジェネレーターの実装が置いてある場所であるため、ここを編集することになる。
両者の具体的な差分は、ebmcg/ 側の ebm2lang::Result 型に CodeWriter が含まれている点(+ それを前提とした default hook 群の差)である。基本的には ebmcg/ のほうが使われることが多いが、インタープリタ、シリアライズ、可視化など CodeWriter 中心の default が合わないケースで ebmip/ として実装する。
機能の追加について
機能の追加は基本的に影響範囲が不定形である。
EBM IR自体の構造を変えない追加であるとしてもそれが例えば全言語間でEBMの解釈を変える必要がある場合影響は全言語のコードジェネレーターに広がりうる。
逆にEBM IRの論理構造を変えてもそれがうまく追従して各言語に影響を及ぼさない可能性もある。だが言語数が増えるに従ってそれぞれの言語にエッジケースの処理のためのコードが存在しているため完全にそうすることはより不可能になってくるであろう。
そのため機能追加の際には必ずリグレッションしていないかを各言語ごとと確認していく必要がある。とくに影響範囲が大きい変更の場合は必ずブランチを切りmainブランチのテスト通過数からリグレッションしていないことを必ず担保する必要がある。
具体的な確認方法については/rebrgen-inter-lang-refactor スキルも参照のこと。
1. EBM IRの構造を変えない追加。
EBM IRの構造を変えない追加とはコード生成ロジックをいじるだけで対処できるような追加のことである。例えばコード生成の際に特定のノードの変換パターンを変えるだけで実現できる場合がこれにあたる。この場合はebmcg/ebm2lang/visitor/内の各言語固有のコードジェネレーターの実装に処理を追加することになる。この方法はEBM IRの構造を変えないため影響範囲を比較的小さく抑えることができる一方で、全言語に影響するような変更の場合は全言語のコードジェネレーターに同様の変更を加える必要があるため注意が必要である。
2. EBM IRの論理構造を変える追加。
EBM IRの論理構造を変える追加とはEBM IRのノードの種類を増やす、あるいはノードの属性を増やすなどは行わずに参照の構造を変えるような追加のことである。例えばあるノードの子ノードの構造を変える、あるいはあるノードの属性を別のノードに移すなどがこれにあたる。この場合はebmgen/transform/内に処理を追加することで対応できる可能性がある。この方法はEBM IRの物理レイアウトを変えないため影響範囲を比較的小さく抑えることができる一方で、既存のIRの論理構造に依存しているコードが多い場合は影響範囲が広くなりすぎる可能性があるため注意が必要である。
3. EBM IRの物理レイアウトを変える追加。
EBM IRの物理レイアウトを変える追加とはEBM IRのノードの種類を増やす、あるいはノードの属性を増やすなどの追加のことである。例えば新しい種類のノードを追加する、あるいは既存のノードに新しい属性を追加するなどがこれにあたる。この場合はextended_binary_module.bgnを編集しupdate_ebm.pyを使って更新する必要がある。またebmgen/transform/内などに処理を追加する必要がある。この方法はEBM IRの構造を変えることができるため柔軟な対応が可能である一方で、EBM IRの構造が変わるため全体への影響が大きくなりがちであるため、変更の内容を十分に理解した上で行う必要がある。ただしこれは変更コストが大きいから躊躇しろというわけではなく必要ならばどんどん行うべきであるが、変更の内容を十分に理解した上で行う必要があるということである.
アーキテクチャ上の考慮点
- EBMの粒度について
EBM IRのノードの粒度はコードジェネレーターの実装のしやすさに大きく影響する。
理想としては各言語が1ノード1言語単位で逐次変換できるような粒度が望ましい。これによりコードジェネレーターの実装が単純になり、また言語ごとのエッジケースの処理も特定のノードに集中させることができるためメンテナンス性も高まる。
が実際には途上でありEBM IRのノードの粒度は必ずしもそうなっているわけではない。特にEBM IRのノードの粒度が粗い場合はコードジェネレーターの実装が複雑になり、また言語ごとのエッジケースの処理も分散してしまうためメンテナンス性が低下する可能性がある。しかしこれは言語ごと固有の詳細をあまりにもEBM IRのノードの粒度に反映させすぎるとEBM IRの構造が複雑になりすぎるため、ある程度は仕方がない部分もある。EBM IRのノードの粒度をどこに設定するかはコードジェネレーターの実装のしやすさとEBM IRの構造の複雑さのバランスを見ながら決めていく必要がある。
- ctx.config()の中身について
ctx.config()は現時点ではctx.visitorの単純なaliasである。その中身については
default_codegen_visitor/visitor/Visitor.hppと各言語固有のvisitor/Visitor_before.hppで定義されている。
特にVisitor_before.hppの方は言語ごとに異なるため注意すること。ctx.config().*_visitorや ctx.config().*_custom, ctx.config().*_wrapperの実体はdefault_codegen_visitor/visitor/Visitor.hppに存在しておりdefault_codegen_visitor/visitor/内のデフォルトフック内で呼び出されている。命名規則の詳細は/rebrgen-inter-lang-refactorを参照。
- 言語数について
言語数はCLAUDE.mdやSKILL.mdに書いてある時点から増えている可能性が高くまた、さらに増える可能性もあるため、そこに書いてある言語個数はあくまで参考程度にとどめて実際のディレクトリ構造とコードを確認すること。特にebmcg/ebm2lang/visitor/内のディレクトリ構造を確認すること。
影響範囲の見積もり方
- brgen DSLの構造を変えるような変更の場合はEBM IRの構造も変わる可能性が高いため、EBM IRの構造を変える追加の影響範囲の見積もり方を参考にすること。
- 当初局所的な変更でで済むと思っても検討を深めていくとEBM IRの構造を変える必要が出てくることもあるため、当初の見積もりに固執せずに柔軟に見積もりを更新していくことが重要である。
- まず、1言語でだけ試してみて影響範囲を見積もることが有効である。特にEBM IRの構造を変えるような変更の場合はまず1言語でだけ試してみて影響範囲を見積もることが有効である。これにより全言語に同様の変更を加える前に問題点を洗い出すことができる。
開発者への対処
- 本質的に新機能追加時には開発者自身もどういう方向にするかが正解かを知っているわけではない。またClaudeでやる分にはいいとしても人間があとからリファクタリングしようとお思うと途方もない変更になるとかいうコードを出すことがよくあるためそのようなことをできるだけ避けることが望ましい。それが結果として後々Claudeを使った場合でも修正漏れをしにくくなりメンテナンス性の向上にもつながる。
- 現時点ではこのドメインのコンテキストについて開発者のほうがよくわかっているため無理にコード構造を推測するよりかは開発者に聞いて方針を決めることも重要である。特にEBM IRの構造を変えるような変更の場合はまず開発者に聞いて方針を決めることが重要である。