Julia ASTs
ジュリアにはコードの2つの表現があります。まず、パーサーによって返される表面構文AST(例えば、Meta.parse関数)があります。これはマクロによって操作されるもので、文字ストリームからjulia-parser.scmによって構築された、書かれたままのコードの構造化された表現です。次に、低下した形式、またはIR(中間表現)があります。これは型推論とコード生成に使用されます。低下した形式では、ノードの種類が少なく、すべてのマクロが展開され、すべての制御フローが明示的な分岐と文のシーケンスに変換されます。低下した形式はjulia-syntax.scmによって構築されます。
まず、マクロを書くために必要なASTに焦点を当てます。
Surface syntax AST
フロントエンドのASTはほぼ完全に Expr と原子(例:シンボル、数値)で構成されています。一般的に、視覚的に異なる各構文形式に対して異なる表現ヘッドがあります。例はs式構文で示されます。各括弧付きリストはExprに対応し、最初の要素がヘッドです。例えば、(call f x) はJuliaの Expr(:call, :f, :x) に対応します。
Calls
| Input | AST |
|---|---|
f(x) | (call f x) |
f(x, y=1, z=2) | (call f x (kw y 1) (kw z 2)) |
f(x; y=1) | (call f (parameters (kw y 1)) x) |
f(x...) | (call f (... x)) |
do 構文:
f(x) do a,b
body
end(do (call f x) (-> (tuple a b) (block body)))として解析されます。
Operators
ほとんどの演算子の使用は単なる関数呼び出しであるため、callというヘッドで解析されます。しかし、一部の演算子は特別な形式(必ずしも関数呼び出しではない)であり、その場合、演算子自体が式のヘッドとなります。julia-parser.scmでは、これらは「構文演算子」と呼ばれています。一部の演算子(+および*)はN-引数解析を使用し、連鎖した呼び出しは単一のN引数呼び出しとして解析されます。最後に、比較の連鎖には独自の特別な式構造があります。
| Input | AST |
|---|---|
x+y | (call + x y) |
a+b+c+d | (call + a b c d) |
2x | (call * 2 x) |
a&&b | (&& a b) |
x += 1 | (+= x 1) |
a ? 1 : 2 | (if a 1 2) |
a,b | (tuple a b) |
a==b | (call == a b) |
1<i<=n | (comparison 1 < i <= n) |
a.b | (. a (quote b)) |
a.(b) | (. a (tuple b)) |
Bracketed forms
| Input | AST |
|---|---|
a[i] | (ref a i) |
t[i;j] | (typed_vcat t i j) |
t[i j] | (typed_hcat t i j) |
t[a b; c d] | (typed_vcat t (row a b) (row c d)) |
t[a b;;; c d] | (typed_ncat t 3 (row a b) (row c d)) |
a{b} | (curly a b) |
a{b;c} | (curly a (parameters c) b) |
[x] | (vect x) |
[x,y] | (vect x y) |
[x;y] | (vcat x y) |
[x y] | (hcat x y) |
[x y; z t] | (vcat (row x y) (row z t)) |
[x;y;; z;t;;;] | (ncat 3 (nrow 2 (nrow 1 x y) (nrow 1 z t))) |
[x for y in z, a in b] | (comprehension (generator x (= y z) (= a b))) |
T[x for y in z] | (typed_comprehension T (generator x (= y z))) |
(a, b, c) | (tuple a b c) |
(a; b; c) | (block a b c) |
Macros
| Input | AST |
|---|---|
@m x y | (macrocall @m (line) x y) |
Base.@m x y | (macrocall (. Base (quote @m)) (line) x y) |
@Base.m x y | (macrocall (. Base (quote @m)) (line) x y) |
Strings
| Input | AST |
|---|---|
"a" | "a" |
x"y" | (macrocall @x_str (line) "y") |
x"y"z | (macrocall @x_str (line) "y" "z") |
"x = $x" | (string "x = " x) |
`a b c` | (macrocall @cmd (line) "a b c") |
ドキュメント文字列の構文:
"some docs"
f(x) = x(macrocall (|.| Core '@doc) (line) "some docs" (= (call f x) (block x))) を解析します。
Imports and such
| Input | AST |
|---|---|
import a | (import (. a)) |
import a.b.c | (import (. a b c)) |
import ...a | (import (. . . . a)) |
import a.b, c.d | (import (. a b) (. c d)) |
import Base: x | (import (: (. Base) (. x))) |
import Base: x, y | (import (: (. Base) (. x) (. y))) |
export a, b | (export a b) |
using は import と同じ表現を持ちますが、式の先頭が :using である点が異なります。
Numbers
Juliaは多くのScheme実装よりも多くの数値型をサポートしているため、すべての数値がAST内で直接Scheme数値として表現されるわけではありません。
| Input | AST |
|---|---|
11111111111111111111 | (macrocall @int128_str nothing "11111111111111111111") |
0xfffffffffffffffff | (macrocall @uint128_str nothing "0xfffffffffffffffff") |
1111...many digits... | (macrocall @big_str nothing "1111....") |
Block forms
ブロックのステートメントは (block stmt1 stmt2 ...) として解析されます。
If ステートメント:
if a
b
elseif c
d
else
e
end解析として:
(if a (block (line 2) b)
(elseif (block (line 3) c) (block (line 4) d)
(block (line 6 e))))while ループは (while condition body) として解析されます。
for ループは (for (= var iter) body) として解析されます。イテレーション仕様が複数ある場合、それらはブロックとして解析されます: (for (block (= v1 iter1) (= v2 iter2)) body)。
break と continue は、0引数の式 (break) と (continue) として解析されます。
let は (let (= var val) body) または (let (block (= var1 val1) (= var2 val2) ...) body) として解析されます。これは for ループのようなものです。
基本的な関数定義は (function (call f x) body) として解析されます。より複雑な例:
function f(x::T; k = 1) where T
return x+1
end解析として:
(function (where (call f (parameters (kw k 1))
(:: x T))
T)
(block (line 2) (return (call + x 1))))型定義:
mutable struct Foo{T<:S}
x::T
end解析されます:
(struct true (curly Foo (<: T S))
(block (line 2) (:: x T)))最初の引数は、型が可変かどうかを示すブール値です。
try ブロックは (try try_block var catch_block finally_block) として解析されます。catch の後に変数がない場合、var は #f です。finally 節がない場合、最後の引数は存在しません。
Quote expressions
Juliaのソース構文形式は、コード引用(quoteおよび :( ))に対して $ を使った補間をサポートしています。Lispの用語で言うと、これは実際には「バッククォート」または「準クォート」形式です。内部的には、補間なしのコード引用の必要性もあります。Juliaのスキームコードでは、補間しない引用は式のヘッド inert で表されます。
inert 表現は Julia の QuoteNode オブジェクトに変換されます。これらのオブジェクトは任意の型の単一の値をラップし、評価されると単にその値を返します。
アトムを引数とする quote 式も QuoteNode に変換されます。
Line numbers
ソース位置情報は (line line_num file_name) の形式で表され、3 番目のコンポーネントはオプションです(現在の行番号が変更される場合は省略されますが、ファイル名は省略されません)。
これらの表現は、JuliaのLineNumberNodeとして表されます。
Macros
マクロの衛生状態は、escape と hygienic-scope というヘッドペアの表現を通じて示されます。マクロ展開の結果は自動的に (hygienic-scope block module) でラップされ、新しいスコープの結果を表します。ユーザーは呼び出し元からコードを補間するために、内部に (escape block) を挿入することができます。
Lowered form
低い形式(IR)はコンパイラにとってより重要です。なぜなら、型推論、インライン化のような最適化、コード生成に使用されるからです。また、入力構文の大幅な再配置から生じるため、人間にはあまり明白ではありません。
Symbolやいくつかの数値型に加えて、以下のデータ型が低下した形式で存在します:
Exprheadフィールドによって示されるノードタイプと、サブ式のVector{Any}であるargsフィールドを持っています。表面ASTのほぼすべての部分はExprで表されますが、IRは主に呼び出しと一部のトップレベル専用の形式のために、限られた数のExprのみを使用します。スロット番号引数とローカル変数を連続番号で識別します。スロットインデックスを示す整数値の
idフィールドがあります。これらのスロットの型は、それらのCodeInfoオブジェクトのslottypesフィールドで確認できます。引数SlotNumberと同じですが、最適化後のみ表示されます。参照されているスロットが囲んでいる関数の引数であることを示します。コード情報一連のステートメントのIRをラップします。その
codeフィールドは実行する式の配列です。GotoNode無条件分岐。引数は分岐先であり、ジャンプするコード配列内のインデックスとして表されます。
GotoIfNot条件分岐。
condフィールドが false に評価される場合、destフィールドによって識別されるインデックスに移動します。ReturnNode引数(
valフィールド)を囲む関数の値として返します。valフィールドが未定義の場合、これは到達不可能なステートメントを表します。QuoteNode任意の値をデータとして参照するためにラップします。例えば、関数
f() = :aは、valueフィールドがシンボルaであるQuoteNodeを含んでおり、評価するのではなくシンボル自体を返すために使用されます。GlobalRefモジュール
modのグローバル変数nameを参照します。SSA値コンパイラによって挿入された、連続番号(1から始まる)の静的単一割り当て(SSA)変数を指します。
SSAValueの番号(id)は、それが表す値の式のコード配列インデックスです。NewvarNode変数(スロット)が作成されるポイントを示します。これにより、変数が未定義にリセットされる効果があります。
Expr types
これらの記号は、Exprのheadフィールドに小文字で表示されます。
コール関数呼び出し(動的ディスパッチ)。
args[1]は呼び出す関数で、args[2:end]は引数です。呼び出す関数呼び出し(静的ディスパッチ)。
args[1]は呼び出す MethodInstance であり、args[2:end]は引数です(呼び出されている関数はargs[2]にあります)。static_parameter静的パラメータをインデックスで参照します。
=課題。IRでは、最初の引数は常に
SlotNumberまたはGlobalRefです。メソッド汎用関数にメソッドを追加し、必要に応じて結果を割り当てます。
1引数形式と3引数形式があります。1引数形式は、構文
function foo endから生じます。1引数形式では、引数はシンボルです。このシンボルが現在のスコープ内で既に関数の名前として使われている場合、何も起こりません。シンボルが未定義の場合、新しい関数が作成され、そのシンボルで指定された識別子に割り当てられます。シンボルが定義されているが非関数の名前を持つ場合、エラーが発生します。「関数の名前を持つ」という定義は、バインディングが定数であり、シングルトン型のオブジェクトを参照することを意味します。これは、シングルトン型のインスタンスがメソッドを追加する型を一意に識別するためです。型にフィールドがある場合、メソッドがインスタンスに追加されるのか、その型に追加されるのかは明確ではありません。3引数形式には以下の引数があります:
args[1]関数名、または不明または不要な場合は
nothing。シンボルの場合、最初の式は上記の1引数形式のように振る舞います。この引数はその後無視されます。メソッドが厳密に型によって追加される場合はnothingであることができます、(::T)(x) = x、または既存の関数にメソッドが追加される場合、MyModule.f(x) = x。args[2]SimpleVectorの引数タイプデータ。args[2][1]は引数タイプのSimpleVectorであり、args[2][2]はメソッドの静的パラメータに対応する型変数のSimpleVectorです。args[3]メソッド自体の
CodeInfo。スコープ外のメソッド定義(異なるスコープで定義されたメソッドを持つ関数にメソッドを追加する)に対しては、:lambda式に評価される式です。
構造体型7つの引数を持つ新しい
structを定義する式:args[1]structの名前args[2]SimpleVectorのパラメータを指定して作成するcall式args[3]fieldnamesを指定してSimpleVectorを作成するcall式args[4]Symbol、GlobalRef、またはExprは、スーパタイプを指定します(例::Integer、GlobalRef(Core, :Any)、または:(Core.apply_type(AbstractArray, T, N)))args[5]SimpleVectorのフィールドタイプを指定するcall式args[6]mutableの場合はtrueのBoolargs[7]初期化に必要な引数の数。これはフィールドの数、または内部コンストラクタの
newステートメントによって呼び出される最小限のフィールドの数になります。
抽象型3つの引数を持つ式で、新しい抽象型を定義します。引数は、
struct_type式の引数1、2、および4と同じです。プリミティブ型4引数の式は、新しいプリミティブ型を定義します。引数1、2、および4は
struct_typeと同じです。引数3はビット数です。グローバルグローバルバインディングを宣言します。
const定数として(グローバル)変数を宣言します。
新しい新しい構造体のようなオブジェクトを割り当てます。最初の引数は型です。
new擬似関数はこれに変換され、型は常にコンパイラによって挿入されます。これは非常に内部専用の機能であり、チェックは行われません。任意のnew式を評価すると、簡単にセグメンテーションフォルトが発生する可能性があります。splatnewnewと似ていますが、フィールド値は単一のタプルとして渡されます。newが第一級関数であった場合のsplat(new)と同様に機能します。そのため、この名前が付けられています。isdefinedExpr(:isdefined, :x)は、xが現在のスコープで既に定義されているかどうかを示す Bool を返します。the_exceptionjl_current_exception(ct)によって返される、catchブロック内で捕捉された例外を返します。エンター例外ハンドラ(
setjmp)に入ります。args[1]は、エラー時にジャンプするキャッチブロックのラベルです。pop_exceptionによって消費されるトークンを生成します。休暇ポップ例外ハンドラー。
args[1]はポップするハンドラーの数です。pop_exceptionキャッチブロックを出るときに、現在の例外のスタックを関連する
enterの状態に戻します。args[1]には関連するenterからのトークンが含まれています。インバウンズ境界チェックをオンまたはオフにするコントロール。スタックが維持され、もしこの式の最初の引数が真または偽(
trueは境界チェックが無効であることを意味します)であれば、それがスタックにプッシュされます。最初の引数が:popの場合、スタックがポップされます。境界チェック@inboundsでマークされたコードのセクションにインライン化された場合はfalseの値を持ち、それ以外の場合はtrueの値を持ちます。ループ情報ループの終了を示します。
LowerSimdLoopに渡されるメタデータを含み、@simd式の内部ループをマークするか、LLVMループパスに情報を伝播させるために使用されます。copyastクォジクォートの実装の一部です。引数は表層構文ASTであり、単純に再帰的にコピーされ、実行時に返されます。
メタメタデータ。
args[1]は通常、メタデータの種類を指定するシンボルであり、残りの引数は自由形式です。以下の種類のメタデータが一般的に使用されます::inlineと:noinline:インラインヒント。
foreigncallccall情報のための静的に計算されたコンテナ。フィールドは次のとおりです:args[1]: 名前外国関数のために解析される式。
args[2]::Type: RT含まれているメソッドが定義されたときに静的に計算される(リテラル)戻り値の型。
args[3]::SimpleVector(の型) : AT含まれているメソッドが定義されたときに静的に計算された引数の型の(リテラル)ベクター。
args[4]::Int: nreqvarargs関数定義に必要な引数の数。
args[5]::QuoteNode{Symbol}: 呼び出し規約呼び出しの規約。
args[6:5+length(args[3])]: 引数すべての引数の値(各引数の型は args[3] に示されています)。
args[6+length(args[3])+1:end]: gc-ルーツ呼び出しの間にgc-rootedされる必要がある追加のオブジェクト。これらがどこから派生し、どのように処理されるかについては、Working with LLVMを参照してください。
new_opaque_closure新しい不透明なクロージャを構築します。フィールドは次のとおりです:
args[1]: 署名不透明クロージャの関数シグネチャ。不透明クロージャはディスパッチに参加しませんが、入力タイプは制限できます。
args[2]: isvaクロージャが可変長引数を受け入れるかどうかを示します。
args[3]: lb出力タイプの下限。 (デフォルトは
Union{})args[4]: ub出力タイプの上限。(デフォルトは
Any)args[5]: メソッドopaque_closure_method表現としての実際のメソッド。args[6:end]: キャプチャ不透明クロージャによってキャプチャされた値。
Method
一意のコンテナが、単一のメソッドに対する共有メタデータを説明します。
名前,モジュール,ファイル,行,シグメタデータは、コンピュータと人間のためにメソッドを一意に識別するためのものです。
あいまいこのメソッドと曖昧な可能性のある他のメソッドのキャッシュ。
専門分野このメソッドのために作成されたすべての MethodInstance のキャッシュで、ユニーク性を確保するために使用されます。ユニーク性は、特にインクリメンタルプリコンパイルやメソッドの無効化の追跡において効率のために必要です。
ソース元のソースコード(利用可能な場合、通常は圧縮されています)。
ジェネレーター特定のメソッドシグネチャに対して専門的なソースを取得するために実行可能な呼び出し可能オブジェクト。
ルーツASTに補間された非ASTのものへのポインタで、ASTの圧縮、型推論、またはネイティブコードの生成に必要です。
nargs、isva、called、is_for_opaque_closure、このメソッドのソースコードに対する説明的ビットフィールド。
プライマリーワールドこのメソッドを「所有する」世界の年齢。
MethodInstance
ユニークなコンテナで、メソッドの単一の呼び出し可能なシグネチャを説明します。特に Proper maintenance and care of multi-threading locks を参照して、これらのフィールドを安全に変更する方法に関する重要な詳細を確認してください。
specTypesこのMethodInstanceの主キー。ユニーク性は
def.specializationsのルックアップを通じて保証されます。defこの関数が記述する
Methodの特化版。あるいは、これはモジュール内で展開されたトップレベルのラムダであり、メソッドの一部ではない場合のModule。sparam_valsspecTypesの静的パラメータの値。Method.unspecializedのMethodInstanceでは、これは空のSimpleVectorです。しかし、MethodTableキャッシュからのランタイムMethodInstanceの場合、これは常に定義されており、インデックス可能です。未推論トップレベルのサンクのための未圧縮ソースコード。さらに、生成された関数の場合、これはソースコードが見つかる多くの場所の1つです。
バックエッジキャッシュ依存関係の逆リストを保存して、新しいメソッド定義後に必要となる可能性のあるインクリメンタル再解析/再コンパイル作業を効率的に追跡します。これは、他の
MethodInstanceのリストを保持することによって機能し、これらはこのMethodInstanceへの可能な呼び出しを含むように推論または最適化されています。これらの最適化結果は、cacheのどこかに保存されているか、定数伝播のようにキャッシュしたくない何かの結果である可能性があります。したがって、ここでさまざまなキャッシュエントリへのすべてのバックエッジをマージします(ほとんどの場合、適用可能なキャッシュエントリは1つだけで、最大ワールドのためのセンチネル値があります)。キャッシュこのテンプレートインスタンスを共有する
CodeInstanceオブジェクトのキャッシュ。
CodeInstance
defこのキャッシュエントリが派生した
MethodInstance。オーナーこの
CodeInstanceの所有者を表すトークンです。jl_egalを使用して一致させます。rettype/rettype_constspecFunctionObjectフィールドの推測される戻り値の型は、一般的に関数全体の計算された戻り値の型でもあります。推測されたこの関数の推測されたソースのキャッシュを含む可能性があるか、単に
rettypeが推測されていることを示すためにnothingに設定される可能性があります。ftpr汎用のjlcallエントリーポイント。
jlcall_apifptrを呼び出す際に使用するABI。いくつかの重要なものには以下が含まれます:- 0 - まだコンパイルされていません
- 1 -
JL_CALLABLEjl_value_t *(*)(jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs) - 2 - 定数(
rettype_constに格納された値) - 3 - 静的パラメータを転送した
jl_value_t *(*)(jl_svec_t *sparams, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs) - 4 - インタープリタで実行
jl_value_t *(*)(jl_method_instance_t *meth, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)
min_world/max_worldこのメソッドインスタンスが呼び出されるのに有効な世界の年齢の範囲。もし max_world が特別なトークン値
-1であれば、その値はまだ知られていません。再考を必要とするバックエッジに遭遇するまで、引き続き使用される可能性があります。
CodeInfo
ソースコードを格納するための(通常は一時的な)コンテナ。
コードAnyステートメントの配列スロット名スロット(引数またはローカル変数)ごとの名前を付けるシンボルの配列。
スロットフラグスロットプロパティの
UInt8配列で、ビットフラグとして表現されています:- 0x02 - 割り当て済み(この変数の左側に割り当て文がない場合のみ偽)
- 0x08 - 使用済み(スロットの読み取りまたは書き込みがある場合)
- 0x10 - 一度静的に割り当てられた
- 0x20 - 割り当て前に使用される可能性があります。このフラグは型推論の後のみ有効です。
ssavaluetypes配列または
Int。Intの場合、関数内のコンパイラによって挿入された一時的な場所の数(code配列の長さ)を示します。配列の場合、各場所の型を指定します。ssaflags関数内の各式に対するステートメントレベルの32ビットフラグ。詳細については、julia.hの
jl_code_info_tの定義を参照してください。リネーテーブルソースロケーションオブジェクトの配列
codelocslinetableに関連付けられた各ステートメントの位置を示す整数インデックスの配列。
オプションフィールド:
スロットタイプスロットのための型の配列。
rettype低下形式(IR)の推測される戻り値の型。デフォルト値は
Anyです。推論制限ヒューリスティクスのためのメソッドmethod_for_inference_heuristicsは、推論中に必要に応じて与えられたメソッドのジェネレーターを拡張します。親このオブジェクトを「所有する」
MethodInstance(該当する場合)。エッジメソッドインスタンスを無効にする必要があるフォワードエッジ。
min_world/max_worldこのコードが推測された時点で有効だった世界の年齢の範囲。
ブールプロパティ:
推測されたこれは型推論によって生成されたものですか。
インライン可能この内容がインライン化の対象となるべきかどうか。
propagate_inboundsこの内容が、
@boundscheckブロックを省略する目的でインライン化されたときに@inboundsを伝播させるべきかどうか。
UInt8 設定:
constprop- 0 = ヒューリスティックを使用する
- 1 = 攻撃的
- 2 = なし
purity5ビットフラグから構成されています:- 0x01 << 0 = このメソッドは一貫して返すか終了することが保証されています (
:consistent) - 0x01 << 1 = このメソッドは外部から意味的に見える副作用がない(
:effect_free) - 0x01 << 2 = このメソッドは例外をスローしないことが保証されています(
:nothrow) - 0x01 << 3 = このメソッドは終了することが保証されています (
:terminates_globally) - 0x01 << 4 = このメソッド内の構文制御フローは終了することが保証されています(
:terminates_locally)
Base.@assume_effectsの詳細については、ドキュメントを参照してください。- 0x01 << 0 = このメソッドは一貫して返すか終了することが保証されています (