Julia のネイティブコード生成の概要
ポインタの表現
コードをオブジェクトファイルに書き出すとき、ポインタはリロケーション情報として書き出されます。そのためコードのデシリアライズで再生成されたオブジェクトに定数の参照が含まれる場合でも、その参照は実行時の正しいオブジェクトを指すことが保証されます。
ポインタ以外のオブジェクトはリテラルの定数として書き出されます。
ポインタのオブジェクトを書き出すのは literal_pointer_val
です。この関数は Julia の値と LLVM のグローバル変数を適切に追跡し、オブジェクトは現在のランタイムとデシリアライズ後のランタイムの両方で正当となります。
オブジェクトに書き出されるとき、こういったグローバル変数は一つの大きな gvals
テーブルに参照として格納されます。これによりデシリアライザは添え字で変数を参照でき、さらにオブジェクトを書き戻すときに Global Offset Table (GOT) に似た独自のマニュアルメカニズムを実装できるようになります。
関数ポインタも同様に処理されます。関数ポインタは値として一つの大きな fvals
テーブルに格納され、グローバル変数と同様にデシリアライザからは添え字で参照できます。
extern
の関数は個別に処理されることに注意してください。名前が割り当てられ、リンカが持つ通常のシンボル解決機構が使われます。
ccall
関数も個別に処理されることにも注意してください。マニュアルの GOT と Procedure Linkage Table (PLT) が使われます。
LLVM IR に含まれる値の表現
値は jl_cgval_t
構造体でやり取りされます。この構造体は右辺値を表現し、他の場所に代入したり渡したりする方法を決定するのに十分な情報を持ちます。
jl_cgval_t
は通常ヘルパーコンストラクタ mark_julia_type
または mark_julia_slot
を通して作られます。mark_julia_type
は即値用で、mark_julia_slot
は値を指すポインタ用です。
convert_julia_type
関数を使うと任意の二つの型の間で変換が可能です。cgval.typ
が typ
となった右辺値が返ります。この関数はオブジェクトを要求された型にキャストするときにヒープボックスを作り、スタックのコピーをアロケートし、さらに表現の変更で必要な場合はタグ付き共用体を計算します。
これに対して update_julia_type
はキャストがゼロコストで (コードを生成せずに) 行えるときに限って cgval.typ
を typ
に変更します。
タグを使った共用体の表現
型が型共用体と推論された値であっても、場合によってはタグの付いた型表現を使ってスタックにアロケートできることがあります。
タグ付き共用体を処理できる必要があるプリミティブルーチンは次の通りです:
- mark-type
- load-local
- store-local
- isa
- is
- emit_typeof
- emit_sizeof
- boxed
- unbox
- 特殊化された cc-ret
これらのプリミティブを使って共用体の分割を実装することで、他のルーチンは推論中に処理できるはずです。
タグ付き共用体は <void* union, byte selector>
という組で表現されます。selector
は固定長であり、selector & 0x7f
が共用体に含まれる最初の 126 個の isbits
型のいずれかを選択するタグとなります。この値は型共用体に対する 1
始まりで深い方から数えた isbits
オブジェクトのカウントを表します。添え字 0
は union*
が実際にはタグの付いたヒープアロケートの jl_value_t*
であり、タグ付き共用体ではなくボックス化オブジェクトとして通常と同じ処理を行う必要があることを示します。
selector
の最上位ビット (selector & 0x80
) を調べれば、union
がヒープアロケートの (jl_value_t*
の) ボックスかどうかを判定できます。この情報により、下位ビットで共用体の分割を効率良く処理しながらもボックスの再アロケートにかかるコストを避けることができます。
値がタグで表現できる限り、byte & 0x7f
が型に対する正確なテストであることは保証されています ──selector = 0x80
にはなりません。そのため isa
をテストするときに型タグをテストする必要はありません。
union
が指すメモリ領域のサイズは決まっておらず、唯一の制限は現在の selector
が指定するデータを保持できることだけです。関連する共用体のフィールドによっては全ての型を一度に保持できるほどの大きさを持たない可能性もあるので、コピーするときには注意が必要です。
特殊化された呼び出し規約シグネチャの表現
jl_returninfo_t
オブジェクトは任意の呼び出し可能オブジェクトの呼び出し規約に関する詳細を表します。
メソッドの引数あるいは返り値のいずれかがボックス化を解除した状態で表現できて、かつメソッドが可変長引数を取らないなら、その引数あるいは返り値には specTypes
と rettype
フィールドに基づいて最適化された呼び出し規約シグネチャが与えられます。
一般的な原則は次の通りです:
- プリミティブ型は整数/浮動小数点数レジスタで渡される。
VecElement
型のタプルはベクトルレジスタで渡される。- 構造体はスタックに載せて渡される。
- 返り値は引数と同様に扱われる。ただし、あるサイズより大きい場合は自動的に
sret
文が使われる。
この処理は get_specsig_function
と deserves_sret
が行います。
また返り値が共用体のときは値の組 (ポインタとタグ) が返る場合があります。その共用体の値がスタックアロケート可能なら、その値を保持できるだけの空間が隠れた第一引数として関数に渡されます。関数から返るポインタが引数に渡されるこの空間を指すのか、それともボックス化オブジェクトまたはその他の定数メモリを指すのかは呼び出し側が決められます。