


マネージドのwrapperとネイティブライブラリを、ABIを壊さずに直す
ネイティブのCライブラリを包む、クロスプラットフォームのマネージドwrapperを直す時に使う。変更をすべての層で追い、古いダウンロードではなくソースからネイティブバイナリをビルドし、公開ABIを保ち、ポインターの所有とスレッドを正しく扱い、失敗するテストを飛ばさない。
このAIルールを使うには
## 変更を、マネージドwrapperからネイティブ実装まで追いかける
ライブラリを、マネージドwrapper、相互運用宣言、安定したC互換API、ネイティブ実装から成る階層システムとして扱う。
- 公開入力はマネージドwrapperで検証する。
- C互換bridgeは小さく予測可能に保つ。方針を重複して実装せず、変換と転送を担わせる。
- マネージド宣言、公開されたCシンボル、パラメーター型、戻り値型、ネイティブ実装を厳密に一致させる。
- コードを編集する前に、各検証ルール、割り当て、エラー変換、ライフタイム遷移をどの層が所有するか決める。
- 最初のコンパイルエラーだけを直すのではなく、影響を受けるすべての層を通して変更を追跡する。
マネージド層でコンパイルできる変更でも、ネイティブバイナリに新しいシンボルがない、またはシグネチャが異なる場合、実行時に失敗する。
## 変更の種類で、ビルド済みとソースビルドのネイティブバイナリを選び分ける
マネージドwrapperをビルドする前にネイティブバイナリが必要だが、その正しい入手元は変更内容によって異なる。
- マネージドのみの作業では、現在リリース済みのmilestone用としてリポジトリが承認したビルド済みネイティブ成果物を使う。
- ネイティブ実装、C API、依存関係、submodule、バージョン、milestoneを変更する場合は、対象プラットフォームとarchitecture向けのネイティブバイナリをソースからビルドする。
- ネイティブ側へ影響する変更後は、その変更が成果物の配布元へ反映されるまで、現在のビルド済みバイナリをダウンロードして使うことを禁止する。
- ネイティブのソースビルドに失敗した場合は、toolchain、依存関係、パス、ネットワークの前提条件を調査する。マネージドビルドを通すためだけに、古いダウンロードへ切り替えてはならない。
- 古いネイティブmoduleを保持して誤ったテスト結果を生む可能性がある場合は、プラットフォームのcacheを消去する。
API変更後に古いネイティブバイナリを使うと、見かけ上は成功した不正なビルドが作られ、その後、実行時にentry point不足が発生する可能性がある。
## bindingは手で直さず再生成し、公開ABIを保つ
生成されたbinding、生成ドキュメント、render済みrelease pageなど、生成物と定められた出力を手動で編集してはならない。編集可能なsourceを変更し、C APIを変更した後はリポジトリのgeneratorを実行する。
安定したapplication binary interfaceを維持する。
- 既存のシグネチャを変更せず、overload、method、typeを追加する。
- 公開APIを廃止したり、戻り値型を変更したりしてはならない。
- 既定パラメーターはbinary compatibilityを変える可能性があるため、新しい既定パラメーターよりoverloadを優先する。
- APIは廃止せずdeprecatedにし、実行可能な移行手順を提示する。
- 命名とパラメーターの規約を、周囲の公開APIと一致させる。
- ビルド前にbindingを再生成し、生成された差分を確認する。
ドキュメントに生成pageと編集可能なsource sidecarがある場合は、指定されたsourceだけを編集して出力を再renderする。
## ネイティブポインターの所有を分け、変更できるobjectをスレッド間で共有しない
wrapperを書く前に、すべてのネイティブpointerを分類する。
- 借用されたパラメーターは所有権を移さない。
- 手動所有のネイティブobjectには、wrapper所定のネイティブ破棄経路が必要となる。
- 参照count方式のobjectは、wrapper所定の参照count契約を使わなければならない。
失敗時の動作を層に合わせる。C互換APIはnull pointerまたは`false`を返し、マネージドfactoryは`null`を返し、有効なobjectを確立できないマネージドconstructorは例外を投げる。
ネイティブ操作によっては、受け取ったものと同じinstanceを返す場合がある。sourceを破棄する前にidentityを比較しなければならない。そうしないと、返された値がすでに解放済みのobjectを参照する可能性がある。
ネイティブ契約が明示的に許可しない限り、変更可能なネイティブobjectをthread間で共有しない。各threadへ専用の変更可能stateを持たせ、immutableかつthread-safeと文書化されたobjectだけを共有する。
固定scopeを越えてネイティブコードが保持する可能性のある、一時固定されたマネージドpointerを渡してはならない。ライフタイムを明示したpinned handleを使うか、unmanaged memoryを確保する。このルールに違反すると、ネイティブコードが古いaddressを保持したままgarbage collectorがmemoryを移動できてしまう。
## 親リポジトリとネイティブsubmoduleの両方でfeature branchを使う
commitを作る前にfeature branchを作成する。保護branchへ直接commitしてはならない。
ネイティブsubmoduleや入れ子のrepositoryにも、同じルールを独立して適用する。それぞれが独自のbranch state、保護branch、commit、review経路を持つ。
1. 親repositoryで目的を絞ったfeature branchを作成する。
2. 入れ子のネイティブrepositoryを変更する場合は、そこで編集またはcommitする前にfeature branchを作成する。
3. すべてのcommitを対応するfeature branch上で作成する。
4. リポジトリが承認したpull requestのworkflowを通して変更をreviewへ提出する。
5. 親repositoryが、review済みの意図したネイティブrevisionを記録していることを確認する。
リポジトリがshimまたは拡張面として指定した範囲外にある、上流管理のネイティブdirectoryを変更しない。
## 正しいネイティブバイナリでテストし、失敗を飛ばさない
ビルド成功だけでは完了にならない。正しいbootstrap経路で生成したネイティブバイナリに対して、関連するマネージドテストを実行する。
- 完了を主張する前に、テストが合格しなければならない。
- 回帰を回避するために失敗中のテストをskipしたり、skip例外を導入したりしてはならない。
- skipを許容できるのは、displayがない、graphics deviceを利用できない、プラットフォームが機能に対応していないなど、実証されたhardwareまたはplatform上の制約に限る。
- C APIを変更した場合は、テスト前にbindingを再生成する。
- version変更を検証する前に、cacheされたネイティブweb成果物やplatform成果物を消去する。
- ネイティブ側へ影響する作業では、代表的なplatformとarchitectureを検証する。
所有権、null処理、公開シンボル、ABI、thread scope、破棄動作が誤っている時に失敗するテストを書く。正確な動作や効果がすでに証明されていない性能変更には、parity testとbenchmarkを含める。
## 失敗の形から、層をまたぐ不具合の場所を見つける
既知の正常baselineを確立し、1回に1つの変数だけを変更し、各結果を記録して、platform差を証拠として扱う。変更によって状態が悪化した場合は、別の仮説を試す前にその変更を戻す。
失敗の形から、最初に調べる境界を選ぶ。
- マネージドtypeが見つからない場合は、bindingが再生成されていないか、含まれていない可能性がある。
- ネイティブシンボルを解決できない場合は、C宣言と実装が一致していない可能性がある。
- 実行時にentry pointが見つからない場合は、古いネイティブバイナリかexportの不一致が強く疑われる。
- access violationは、所有権、破棄、pinning、シグネチャの破損を示唆する。
- factory呼び出し後のnull reference失敗は、ネイティブ層が失敗を返したのにcallerが無視した可能性を示す。
- 不規則なthread間crashは、変更可能なネイティブobjectが共有された可能性を示す。
完了前に、生成された差分を確認し、必要な場合はネイティブコードをビルドし、マネージドwrapperをビルドしてテストを実行し、影響するplatform matrixを検証する。使用したbootstrapの入手元とarchitectureを正確に報告する。テストが失敗している、必要なネイティブビルドをダウンロードで代用した、またはplatform検証が未完了の状態で成功を主張してはならない。
