# 第28b章: 分離コンパイル [English](README.md) | 日本語 [Previous: Chapter 28a](../chapter28a/README.ja.md) 前の章では、見つかったソースを一度にコンパイルしました。この章ではライブラリと クライアントを別々にコンパイルします。クライアントにはライブラリのオブジェクト ファイルが必要ですが、そのソースは不要です。 ## オブジェクトに理想 IR を保存する 通常の ELF オブジェクトには機械語、データ、シンボル、再配置情報があります。 Simple はさらに `.simple` セクションへ型と理想 IR を保存します。クライアントは 宣言を読み込んで型を検査し、関数本体も読み込んで SCCP やインライン化に使います。 残った呼び出しは、ネイティブリンクでライブラリの機械語につなぎます。 宣言型と解析中の型は別々に保存します。ある時点でフィールドの値が 0 でも、 その宣言を定数 0 に変えてはいけません。引数の型の上限、数値演算のモード、 Store の幅、元のソース位置も保存します。入れ子の構造体には、関数本体だけでなく 割り当て関数とコンストラクタの宣言も必要です。 別々のコンパイルで同じ番号になったエイリアスや関数は、同じものとは限りません。 `GlobalBits` は定義元のファイルとファイル内の番号を保存し、インポート時にその 組み合わせを今回の番号へ写します。型とノードに同じ写像を使い、複数ファイルを またぐ再帰型も復元します。 読み込みの単位は現在オブジェクトです。無関係なオブジェクトは読み込まずに済みますが、選ばれた オブジェクトの保存済み IR は全体を復元し、記録された依存先も読み込みます。`sys.io.p` だけを 呼ぶ場合でも、`sys.o` にまとめられた十個の単位をすべて読み込み、`aryu8` と `aryi64` も含みます。 必要なメンバーだけの読み込みは今後の目標です。IR の読み込みだけでクラスが初期化されたり、 その本体がクライアントの出力にコピーされたりするわけではありません。元のソースの解析は省けますが、 インポートしたグラフも新しい呼び出し元とともに最適化し、型検査します。 ## 実行できる例 英語版の例は次の二つのソースルートにあります。 ```text src/test/smp/separate/library/Counters.smp src/test/smp/separate/client/Main.smp ``` ライブラリは、ループで final フィールドを計算する `Counter` と共有カウンタを 公開します。クライアントの `run(3)` は最初に 107 を返します。同じプロセスで 続けて `run(5)` を呼ぶと 231 になります。インスタンスは別々ですが、クラスの カウンタは共有されます。`run(0)` はライブラリを使わずに戻ります。 このディレクトリでライブラリをコンパイルします。 ```sh make lib make build/classes/test/.ttag java -ea -cp "build/classes/main:build/classes/test:lib/*" \ com.seaofnodes.simple.Simple --norun -o build/separate-example/lib/ \ src/test/smp/separate/library/Counters.smp ``` 次にオブジェクトを使ってクライアントを評価します。 ```sh java -ea -cp "build/classes/main:build/classes/test:lib/*" \ com.seaofnodes.simple.Simple --eval \ -L build/separate-example/lib/Counters.o \ src/test/smp/separate/client/Main.smp 3 ``` 結果は `107` です。Windows のクラスパス区切りには `;` を使います。クライアントの ソースルートには `Counters.smp` がありません。`SeparateCompilationTest` はさらに 二つのオブジェクトを C ドライバとリンクし、繰り返し呼び出す動作を検査します。 ## 解析とコードの所有者 インポートした本体は解析に参加しても、クライアントの出力には属しません。 `Serialize` は `Lower` がクラス初期化を展開する前に理想 IR を保存します。 `Unlink` は機械語生成に向けてインポートした本体と解析用の呼び出し辺を切り離します。 インライン化でクライアントへコピーされたコードは、クライアント自身の本体として 出力されます。 初期化規則は前の章と同じです。型のインポートだけでは初期化しません。能動的な使用は `CLInit` を通して初期化を要求し、ライブラリが持つクラスオブジェクトとフラグを使います。 依存関係の検査にはインポートした本体も含めます。ネイティブコードへ渡した関数の 未知の呼び出し元も保持します。 コンストラクタのフィールド更新はローカル値と Phi のままで、ヘルパーの出口に Store を 置きます。未完成のオブジェクトでも初期化済みフィールドは読めます。 アンダースコアは公開インターフェースの選択に使いますが、ソースのアクセス制限は まだ実装されていません。 ## 検証と今後 明示的に指定したソースは常にコンパイルします。他のローカルオブジェクトを再利用する前に、 記録された依存関係をたどり、各オブジェクトにまとめられた全ソースを検査します。 入力の欠落や、より新しいソース・依存オブジェクトがあれば、保存した型や IR を使わず 再解析します。A が B をインライン化していれば、B のオブジェクトが未更新でも、 B のソース変更で A は無効になります。循環は訪問済み集合で処理します。 ライブラリは検査と読み込みで同じ検索パスを使います。ソースのないライブラリは整合した 組み合わせで提供する必要があります。時刻だけでは互換性は判断できません。入力ハッシュ、 コンパイラ・ABI のバージョン、自動再リンクを含むビルドドライバは今後の課題です。 リポジトリのルートから次を実行します。 ```sh make tests CHAPTERS=chapter28b ``` 保存したコンストラクタ、インポートをまたぐ初期化循環、共有データ、ネイティブ関数の サンク、ライブラリとクライアントの実行を検査します。Make はテスト用の `sys` も オブジェクトにします。`make release` は jar と標準ライブラリを作り、`make view` は 共通のグラフビューアーを起動します。 `IncrementalBuildTest` は時刻を明示的に設定し、再利用、間接的な変更、まとめられた 循環、入力の欠落、ライブラリ更新を検査します。変更後のネイティブ実行結果も クリーンビルドと比較します。 保存形式は `C0DE` 以外のバージョン番号をまだ持ちません。コンパイラや形式を変えたら オブジェクトを再構築してください。高コストの往復一致検査は無効のままで、古い増分 ビルドの実験 `testModule0` も実行対象外です。 ネイティブライブラリの構築と最適化の調整は[付録](../appendices/README.md)にまとめています。