blob: 0c565a76d5f038b3d8c44fab476dfba8d7837393 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
# 第28b章: 分離コンパイル
[English](README.md) | 日本語
[Previous: Chapter 28a](../chapter28a/README.ja.md)
前の章では、見つかったソースを一度にコンパイルしました。この章ではライブラリと
クライアントを別々にコンパイルします。クライアントにはライブラリのオブジェクト
ファイルが必要ですが、そのソースは不要です。
## オブジェクトに理想 IR を保存する
通常の ELF オブジェクトには機械語、データ、シンボル、再配置情報があります。
Simple はさらに `.simple` セクションへ型と理想 IR を保存します。クライアントは
宣言を読み込んで型を検査し、関数本体も読み込んで SCCP やインライン化に使います。
残った呼び出しは、ネイティブリンクでライブラリの機械語につなぎます。
宣言型と解析中の型は別々に保存します。ある時点でフィールドの値が 0 でも、
その宣言を定数 0 に変えてはいけません。引数の型の上限、数値演算のモード、
Store の幅、元のソース位置も保存します。入れ子の構造体には、関数本体だけでなく
割り当て関数とコンストラクタの宣言も必要です。
別々のコンパイルで同じ番号になったエイリアスや関数は、同じものとは限りません。
`GlobalBits` は定義元のファイルとファイル内の番号を保存し、インポート時にその
組み合わせを今回の番号へ写します。型とノードに同じ写像を使い、複数ファイルを
またぐ再帰型も復元します。
コンパイル単位ごとに `sys.o`、`sys/io.o`、`sys/libc.o` などを出力します。宣言と関数の
要約は本体より先に保存し、IR を復元せずに読めます。未ロードの関数は `FunNode`、
`SummaryNode`、`ReturnNode` で表し、戻り値・メモリ・エスケープ情報を解析に使います。
要約そのものはインライン化しません。ワークリストが空になった時点で、保存したサイズを
参考に本体を要求し、読み込んで解析してから実際のインライン化を判断します。
`sys.io.p` を通常の呼び出しとして残す場合、`io` の IR は不要です。`io.o` の本体を
読み込んでも、その中の外部参照には型があり、別の本体を自動的に要求しません。
メタデータの探索は保守的で、要求したオブジェクト内の関数本体はまとめて読み込みます。
さらに細かい関数単位の読み込みは今後の目標です。
要約には直接呼び出しとクラス初期化の依存辺も保存します。循環の可能性が見つかったら、
残りの本体をすべて読み込み、最適化後に再検査してからエラーを報告します。循環の外の
ヘルパーが返す定数によって、循環内の辺が消える場合もあるためです。
コンパイル直後のネイティブリンクは CompUnit に保存されたオブジェクトのパスを使います。
コンパイラの状態を持たず保存済みオブジェクトだけをリンクするテストでは、必要な
オブジェクトをすべて明示的に列挙します。必要なファイルが欠けていればリンクに失敗します。
ネイティブ実行では実行時の動的ロードは行いません。
Eval2 も実行前の解析時に依存オブジェクトを解決します。型検査済みプログラムの
オブジェクトは、互いに整合するビルドから提供されることを前提とします。
実行しない分岐からの依存でも、必要なオブジェクトがなければ失敗します。
Eval2 は型検査済みのプログラムを実行し、呼び出しで必要になったオブジェクトの IR だけを
復元して個別にスケジュールします。これはリンク済みプログラムの準備を遅らせる仕組みで、
実行中に新しいライブラリを探索するものではありません。実行中のグラフの再最適化は行いません。
理想ノードの CLInit を直接実行します。子の `<clinit>` は親の初期化を確認してから、
自分の `$init` フラグを設定し、本体を実行します。Eval2 と Lower 後のコードでこの順序を
共有し、呼び出し側は対象クラスのフラグだけを検査します。
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` は時刻を明示的に設定し、再利用、間接的な変更、オブジェクト間の
循環、入力の欠落、ライブラリ更新を検査します。変更後のネイティブ実行結果も
クリーンビルドと比較します。
`SelectiveLoadingTest` は要約による呼び出し、インライン化のためのロード、静的リンク、
初期化循環の再検査を確認します。
保存形式は `C0DE` 以外のバージョン番号をまだ持ちません。コンパイラや形式を変えたら
オブジェクトを再構築してください。高コストの往復一致検査は無効のままで、古い増分
ビルドの実験 `testModule0` も実行対象外です。
ネイティブライブラリの構築と最適化の調整は[付録](../docs/appendices/README.md)にまとめています。
|