EscapeAnalysis
Core.Compiler.EscapeAnalysis
は、Julia's SSA-form IR、すなわち IRCode
のエスケープ情報を分析することを目的としたコンパイラユーティリティモジュールです。
このエスケープ分析の目的は:
- ジュリアの高レベルのセマンティクスを活用し、特に手続き間呼び出しを通じてエスケープとエイリアシングについて考察します。
- さまざまな最適化に使用できるように柔軟である必要があります。これには、alias-aware SROA、early
finalize
insertion、copy-freeImmutableArray
construction、可変オブジェクトのスタック割り当てなどが含まれます。 - 単純な実装を達成するために、完全な逆データフロー分析の実装と、直交格子特性を組み合わせた新しい格子設計に基づいています。
Try it out!
EAUtils.jl
ユーティリティスクリプトをロードすることで、エスケープ分析を試すことができます。このスクリプトは、テストおよびデバッグ目的のために便利なエントリ code_escapes
と @code_escapes
を定義しています。
julia> let JULIA_DIR = normpath(Sys.BINDIR, "..", "share", "julia") # load `EscapeAnalysis` module to define the core analysis code include(normpath(JULIA_DIR, "base", "compiler", "ssair", "EscapeAnalysis", "EscapeAnalysis.jl")) using .EscapeAnalysis # load `EAUtils` module to define the utilities include(normpath(JULIA_DIR, "test", "compiler", "EscapeAnalysis", "EAUtils.jl")) using .EAUtils end
julia> mutable struct SafeRef{T} x::T end
julia> Base.getindex(x::SafeRef) = x.x;
julia> Base.setindex!(x::SafeRef, v) = x.x = v;
julia> Base.isassigned(x::SafeRef) = true;
julia> get′(x) = isassigned(x) ? x[] : throw(x);
julia> result = code_escapes((String,String,String,String)) do s1, s2, s3, s4 r1 = Ref(s1) r2 = Ref(s2) r3 = SafeRef(s3) try s1 = get′(r1) ret = sizeof(s1) catch err global GV = err # will definitely escape `r1` end s2 = get′(r2) # still `r2` doesn't escape fully s3 = get′(r3) # still `r3` doesn't escape fully s4 = sizeof(s4) # the argument `s4` doesn't escape here return s2, s3, s4 end
#1(X s1::String, ↑ s2::String, ↑ s3::String, ✓ s4::String) in Main at REPL[7]:2 X 1 ── %1 = %new(Base.RefValue{String}, _2)::Base.RefValue{String} *′ │ %2 = %new(Base.RefValue{String}, _3)::Base.RefValue{String} ✓′ └─── %3 = %new(Main.SafeRef{String}, _4)::Main.SafeRef{String} ✓′ 2 ── %4 = ϒ (%3)::Main.SafeRef{String} *′ │ %5 = ϒ (%2)::Base.RefValue{String} ✓ │ %6 = ϒ (_5)::String ◌ └─── %7 = enter #8 ◌ 3 ── %8 = Base.isdefined(%1, :x)::Bool ◌ └─── goto #5 if not %8 X 4 ── Base.getfield(%1, :x)::String ◌ └─── goto #6 ◌ 5 ── Main.throw(%1)::Union{} ◌ └─── unreachable ◌ 6 ── $(Expr(:leave, :(%7))) ◌ 7 ── goto #11 ✓′ 8 ┄─ %16 = φᶜ (%4)::Main.SafeRef{String} *′ │ %17 = φᶜ (%5)::Base.RefValue{String} ✓ │ %18 = φᶜ (%6)::String X └─── %19 = $(Expr(:the_exception))::Any ◌ 9 ── nothing::Nothing ◌ 10 ─ (Main.GV = %19)::Any ◌ └─── $(Expr(:pop_exception, :(%7)))::Core.Const(nothing) ✓′ 11 ┄ %23 = φ (#7 => %3, #10 => %16)::Main.SafeRef{String} *′ │ %24 = φ (#7 => %2, #10 => %17)::Base.RefValue{String} ✓ │ %25 = φ (#7 => _5, #10 => %18)::String ◌ │ %26 = Base.isdefined(%24, :x)::Bool ◌ └─── goto #13 if not %26 ↑ 12 ─ %28 = Base.getfield(%24, :x)::String ◌ └─── goto #14 ◌ 13 ─ Main.throw(%24)::Union{} ◌ └─── unreachable ↑ 14 ─ %32 = Base.getfield(%23, :x)::String ◌ │ %33 = Core.sizeof(%25)::Int64 ↑′ │ %34 = Core.tuple(%28, %32, %33)::Tuple{String, String, Int64} ◌ └─── return %34
各呼び出し引数およびSSAステートメントの横にある記号は、以下の意味を表します:
◌
(plain): この値は分析されません。なぜなら、そのエスケープ情報は使用されないからです(例えば、オブジェクトがisbitstype
の場合)。✓
(緑またはシアン): この値は決して逃げません(has_no_escape(result.state[x])
が成立します)、引数の逃げがある場合は青色になります(has_arg_escape(result.state[x])
が成立します)↑
(青または黄): この値は呼び出し元に戻ることでエスケープできます(has_return_escape(result.state[x])
が成立する場合)、未処理のスローエスケープがある場合は黄色に色付けされます(has_thrown_escape(result.state[x])
が成立する場合)。X
(赤): この値は、グローバルメモリへのエスケープのように、エスケープ分析が推論できない場所にエスケープする可能性があります(has_all_escape(result.state[x])
が成り立ちます)。*
(太字): この値のエスケープ状態は、ReturnEscape
とAllEscape
の部分順序の間にありますEscapeInfo
、未処理のスローエスケープがある場合は黄色に色付けされます(has_thrown_escape(result.state[x])
が成立する場合)。′
: この値は、そのAliasInfo
プロパティに追加のオブジェクトフィールド / 配列要素情報を持っています。
各呼び出し引数およびSSA値のエスケープ情報は、次のようにプログラム的に検査できます:
julia> result.state[Core.Argument(3)] # get EscapeInfo of `s2`
ReturnEscape
julia> result.state[Core.SSAValue(3)] # get EscapeInfo of `r3`
NoEscape′
Analysis Design
Lattice Design
EscapeAnalysis
は、data-flow analysisとして実装されており、x::EscapeInfo
の格子上で動作します。これは以下のプロパティで構成されています:
x.Analyzed::Bool
: 格子の正式な一部ではなく、単にx
が分析されていないか、またはそうでないことを示します。x.ReturnEscape::BitSet
: 呼び出し元に戻ることでx
がエスケープできるSSAステートメントを記録しますx.ThrownEscape::BitSet
: 例外として投げられる可能性のあるSSAステートメントを記録します(以下に説明するexception handlingで使用されます)x.AliasInfo
:x
のフィールドや配列要素にエイリアスできるすべての可能な値を保持します(以下に説明するalias analysisで使用されます)x.ArgEscape::Int
(まだ実装されていません):引数に対してsetfield!
を通じて呼び出し元にエスケープすることを示します。
これらの属性は、入力プログラムが有限の文の数を持つという不変条件がジュリアのセマンティクスによって保証されていることを考慮して、有限の高さを持つ部分格子を作成するために組み合わせることができます。この格子設計の巧妙な部分は、各格子プロパティを個別に処理できるようにすることで、格子操作の実装を簡素化できる点です[LatticeDesign]。
Backward Escape Propagation
このエスケープ分析の実装は、論文[MM02]で説明されているデータフローアルゴリズムに基づいています。この分析はEscapeInfo
のラティス上で動作し、すべてのラティス要素が収束点に到達するまで、ラティス要素を下から上へと遷移させます。これは、分析対象の残りのSSAステートメントに対応するプログラムカウンタを含む(概念的な)作業セットを維持することによって行われます。この分析は、各引数とSSAステートメントのEscapeInfo
を追跡する単一のグローバル状態を管理しますが、EscapeInfo
のReturnEscape
プロパティに記録されたプログラムカウンタとしてエンコードされた一部のフロー感度も考慮されており、必要に応じて支配分析と組み合わせてフロー感度について推論することができます。
このエスケープ分析の特徴的なデザインは、それが完全に 逆方向 であることです。つまり、エスケープ情報は 使用から定義へ 流れます。例えば、以下のコードスニペットでは、EAは最初にステートメント return %1
を分析し、%1
(obj
に対応)に ReturnEscape
を課します。そして、次に %1 = %new(Base.RefValue{String, _2}))
を分析し、%1
に課された ReturnEscape
を呼び出し引数 _2
(s
に対応)に伝播させます。
julia> code_escapes((String,)) do s obj = Ref(s) return obj end
#3(↑ s::String) in Main at REPL[1]:2 ↑′ 1 ─ %1 = %new(Base.RefValue{String}, _2)::Base.RefValue{String} ◌ └── return %1
ここでの重要な観察は、この逆向きの分析が使用-定義チェーンに沿って自然にエスケープ情報を流すことを可能にするということです[BackandForth]。その結果、このスキームはエスケープ分析のシンプルな実装を可能にします。例えば、PhiNode
は、PhiNode
に課せられたエスケープ情報をその前駆値に伝播させることで簡単に処理できます:
julia> code_escapes((Bool, String, String)) do cnd, s, t if cnd obj = Ref(s) else obj = Ref(t) end return obj end
#5(✓ cnd::Bool, ↑ s::String, ↑ t::String) in Main at REPL[1]:2 ◌ 1 ─ goto #3 if not _2 ↑′ 2 ─ %2 = %new(Base.RefValue{String}, _3)::Base.RefValue{String} ◌ └── goto #4 ↑′ 3 ─ %4 = %new(Base.RefValue{String}, _4)::Base.RefValue{String} ↑′ 4 ┄ %5 = φ (#2 => %2, #3 => %4)::Base.RefValue{String} ◌ └── return %5
Alias Analysis
EscapeAnalysis
は、特定の精度でオブジェクトフィールドに課せられたエスケープについて推論するために、逆向きフィールド分析を実装しています。また、x::EscapeInfo
のx.AliasInfo
プロパティはこの目的のために存在します。これは、「使用」サイトでx
のフィールドにエイリアスされる可能性のあるすべての値を記録し、その後、記録された値のエスケープ情報が「定義」サイトで実際のフィールド値に伝播されます。より具体的には、分析はgetfield
呼び出しを分析することによってオブジェクトのフィールドにエイリアスされる可能性のある値を記録し、その後、%new(...)
式またはsetfield!
呼び出しを分析する際にそのエスケープ情報をフィールドに伝播させます[Dynamism]。
julia> code_escapes((String,)) do s obj = SafeRef("init") obj[] = s v = obj[] return v end
#7(↑ s::String) in Main at REPL[1]:2 ◌ 1 ─ return _2
上記の例では、ReturnEscape
が%3
(v
に対応)に課せられていますが、これは直接%1
(obj
に対応)に伝播されるのではなく、ReturnEscape
は_2
(s
に対応)にのみ伝播されます。ここで、%3
は%1
のAliasInfo
プロパティに記録されており、これは%1
の最初のフィールドにエイリアスされる可能性があるためです。そして、Base.setfield!(%1, :x, _2)::String
を分析する際に、そのエスケープ情報は_2
に伝播されますが、%1
には伝播されません。
EscapeAnalysis
は、オブジェクトフィールドのエスケープを分析するために、getfield
-%new
/setfield!
チェーンを通じてエイリアスされることができるIR要素を追跡しますが、実際にはこのエイリアス分析は他のIR要素も処理できるように一般化する必要があります。これは、JuliaのIRでは同じオブジェクトが異なるIR要素によって表現されることがあるためであり、実際に同じオブジェクトを表すことができる異なるIR要素が同じエスケープ情報を共有することを確認する必要があります。オペランドとして同じオブジェクトを返すIR要素、例えばPiNode
やtypeassert
は、IRレベルのエイリアシングを引き起こす可能性があり、そのため、エイリアスされた値のいずれかに課せられたエスケープ情報をそれらの間で共有する必要があります。さらに興味深いことに、PhiNode
の変異について正しく推論するためにも必要です。次の例を考えてみましょう:
julia> code_escapes((Bool, String,)) do cond, x if cond ϕ2 = ϕ1 = SafeRef("foo") else ϕ2 = ϕ1 = SafeRef("bar") end ϕ2[] = x y = ϕ1[] return y end
#9(✓ cond::Bool, ↑ x::String) in Main at REPL[1]:2 ◌ 1 ─ goto #3 if not _2 ✓′ 2 ─ %2 = %new(Main.SafeRef{String}, "foo")::Main.SafeRef{String} ◌ └── goto #4 ✓′ 3 ─ %4 = %new(Main.SafeRef{String}, "bar")::Main.SafeRef{String} ✓′ 4 ┄ %5 = φ (#2 => %2, #3 => %4)::Main.SafeRef{String} ✓′ │ %6 = φ (#2 => %2, #3 => %4)::Main.SafeRef{String} ◌ │ Base.setfield!(%5, :x, _3)::String ↑ │ %8 = Base.getfield(%6, :x)::String ◌ └── return %8
ϕ1 = %5
と ϕ2 = %6
はエイリアスされているため、ReturnEscape
が %8 = Base.getfield(%6, :x)::String
(y = ϕ1[]
に対応)に課せられ、これを Base.setfield!(%5, :x, _3)::String
(ϕ2[] = x
に対応)に伝播させる必要があります。このようなエスケープ情報が正しく伝播されるためには、分析が ϕ1
と ϕ2
の 前駆体 もエイリアスされる可能性があることを認識し、それらのエスケープ情報を等しくする必要があります。
そのようなエイリアス情報の興味深い特性の一つは、それが「使用」サイトでは知られておらず、「定義」サイトでのみ導出できるということです(エイリアスは概念的に代入と同等であるため)、したがって、自然に逆方向の分析には適合しません。関連する値間でエスケープ情報を効率的に伝播させるために、EscapeAnalysis.jlは古いJVM論文[JVM05]で説明されているエスケープ分析アルゴリズムに触発されたアプローチを使用しています。つまり、エスケープラティス要素を管理することに加えて、分析は「等価」エイリアスセットも維持します。これは、エイリアスされた引数とSSAステートメントの互いに排他的なセットです。エイリアスセットは、お互いにエイリアス可能な値を管理し、これらのエイリアスされた値のいずれかに課せられたエスケープ情報をそれらの間で等しくすることを可能にします。
Array Analysis
上記で説明したオブジェクトフィールドのエイリアス分析は、配列操作を分析するために一般化することもできます。 EscapeAnalysis
は、さまざまなプリミティブ配列操作の処理を実装しており、arrayref
-arrayset
の使用-定義チェーンを介してエスケープを伝播させ、割り当てられた配列が過度にエスケープしないようにします。
julia> code_escapes((String,)) do s ary = Any[] push!(ary, SafeRef(s)) return ary[1], length(ary) end
#11(X s::String) in Main at REPL[1]:2 X 1 ── %1 = Core.getproperty(Memory{Any}, :instance)::Memory{Any} X │ %2 = invoke Core.memoryref(%1::Memory{Any})::MemoryRef{Any} X │ %3 = %new(Vector{Any}, %2, (0,))::Vector{Any} X │ %4 = %new(Main.SafeRef{String}, _2)::Main.SafeRef{String} X │ %5 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %6 = Base.getfield(%5, :mem)::Memory{Any} X │ %7 = Base.getfield(%6, :length)::Int64 X │ %8 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %9 = $(Expr(:boundscheck, true))::Bool X │ %10 = Base.getfield(%8, 1, %9)::Int64 ◌ │ %11 = Base.add_int(%10, 1)::Int64 ◌ │ %12 = Base.memoryrefoffset(%5)::Int64 X │ %13 = Core.tuple(%11)::Tuple{Int64} ◌ │ Base.setfield!(%3, :size, %13)::Tuple{Int64} ◌ │ %15 = Base.add_int(%12, %11)::Int64 ◌ │ %16 = Base.sub_int(%15, 1)::Int64 ◌ │ %17 = Base.slt_int(%7, %16)::Bool ◌ └─── goto #3 if not %17 X 2 ── %19 = %new(Base.var"#133#134"{Vector{Any}, Int64, Int64, Int64, Int64, Int64, Memory{Any}, MemoryRef{Any}}, %3, %16, %12, %11, %10, %7, %6, %5)::Base.var"#133#134"{Vector{Any}, Int64, Int64, Int64, Int64, Int64, Memory{Any}, MemoryRef{Any}} ✓ └─── invoke %19()::MemoryRef{Any} ◌ 3 ┄─ goto #4 X 4 ── %22 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %23 = $(Expr(:boundscheck, true))::Bool X │ %24 = Base.getfield(%22, 1, %23)::Int64 X │ %25 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %26 = Base.memoryrefnew(%25, %24, false)::MemoryRef{Any} X │ Base.memoryrefset!(%26, %4, :not_atomic, false)::Main.SafeRef{String} ◌ └─── goto #5 ◌ 5 ── %29 = $(Expr(:boundscheck, true))::Bool ◌ └─── goto #9 if not %29 ◌ 6 ── %31 = Base.sub_int(1, 1)::Int64 ◌ │ %32 = Base.bitcast(Base.UInt, %31)::UInt64 X │ %33 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %34 = $(Expr(:boundscheck, true))::Bool X │ %35 = Base.getfield(%33, 1, %34)::Int64 ◌ │ %36 = Base.bitcast(Base.UInt, %35)::UInt64 ◌ │ %37 = Base.ult_int(%32, %36)::Bool ◌ └─── goto #8 if not %37 ◌ 7 ── goto #9 ◌ 8 ── %40 = Core.tuple(1)::Tuple{Int64} ✓ │ invoke Base.throw_boundserror(%3::Vector{Any}, %40::Tuple{Int64})::Union{} ◌ └─── unreachable X 9 ┄─ %43 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %44 = Base.memoryrefnew(%43, 1, false)::MemoryRef{Any} X │ %45 = Base.memoryrefget(%44, :not_atomic, false)::Any ◌ └─── goto #10 X 10 ─ %47 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %48 = $(Expr(:boundscheck, true))::Bool X │ %49 = Base.getfield(%47, 1, %48)::Int64 ↑′ │ %50 = Core.tuple(%45, %49)::Tuple{Any, Int64} ◌ └─── return %50
上記の例では、EscapeAnalysis
は%20
と%2
(割り当てられたオブジェクトSafeRef(s)
に対応)がarrayset
-arrayref
チェーンを介してエイリアスされていることを理解し、これにReturnEscape
を適用しますが、割り当てられた配列%1
(ary
に対応)には適用しません。EscapeAnalysis
は、BoundsError
を介した潜在的なエスケープを考慮する必要があるため、ary
に対してThrownEscape
を適用しますが、このような未処理のThrownEscape
は、ary
の割り当てを最適化する際には無視されることが多いことにも注意してください。
さらに、配列インデックス情報と配列の次元が正確にわかる場合、EscapeAnalysis
はarrayref
-arrayset
チェーンを介して「要素ごとの」エイリアシングについても推論することができます。これは、EscapeAnalysis
がオブジェクトに対して「フィールドごとの」エイリアス分析を行うのと同様です。
julia> code_escapes((String,String)) do s, t ary = Vector{Any}(undef, 2) ary[1] = SafeRef(s) ary[2] = SafeRef(t) return ary[1], length(ary) end
#13(X s::String, X t::String) in Main at REPL[1]:2 X 1 ── %1 = $(Expr(:foreigncall, :(:jl_alloc_genericmemory), Ref{Memory{Any}}, svec(Any, Int64), 0, :(:ccall), Memory{Any}, 2, 2))::Memory{Any} X │ %2 = Core.memoryrefnew(%1)::MemoryRef{Any} X │ %3 = %new(Vector{Any}, %2, (2,))::Vector{Any} X │ %4 = %new(Main.SafeRef{String}, _2)::Main.SafeRef{String} ◌ │ %5 = $(Expr(:boundscheck, true))::Bool ◌ └─── goto #5 if not %5 ◌ 2 ── %7 = Base.sub_int(1, 1)::Int64 ◌ │ %8 = Base.bitcast(Base.UInt, %7)::UInt64 X │ %9 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %10 = $(Expr(:boundscheck, true))::Bool X │ %11 = Base.getfield(%9, 1, %10)::Int64 ◌ │ %12 = Base.bitcast(Base.UInt, %11)::UInt64 ◌ │ %13 = Base.ult_int(%8, %12)::Bool ◌ └─── goto #4 if not %13 ◌ 3 ── goto #5 ◌ 4 ── %16 = Core.tuple(1)::Tuple{Int64} ✓ │ invoke Base.throw_boundserror(%3::Vector{Any}, %16::Tuple{Int64})::Union{} ◌ └─── unreachable X 5 ┄─ %19 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %20 = Base.memoryrefnew(%19, 1, false)::MemoryRef{Any} X │ Base.memoryrefset!(%20, %4, :not_atomic, false)::Main.SafeRef{String} ◌ └─── goto #6 X 6 ── %23 = %new(Main.SafeRef{String}, _3)::Main.SafeRef{String} ◌ │ %24 = $(Expr(:boundscheck, true))::Bool ◌ └─── goto #10 if not %24 ◌ 7 ── %26 = Base.sub_int(2, 1)::Int64 ◌ │ %27 = Base.bitcast(Base.UInt, %26)::UInt64 X │ %28 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %29 = $(Expr(:boundscheck, true))::Bool X │ %30 = Base.getfield(%28, 1, %29)::Int64 ◌ │ %31 = Base.bitcast(Base.UInt, %30)::UInt64 ◌ │ %32 = Base.ult_int(%27, %31)::Bool ◌ └─── goto #9 if not %32 ◌ 8 ── goto #10 ◌ 9 ── %35 = Core.tuple(2)::Tuple{Int64} ✓ │ invoke Base.throw_boundserror(%3::Vector{Any}, %35::Tuple{Int64})::Union{} ◌ └─── unreachable X 10 ┄ %38 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %39 = Base.memoryrefnew(%38, 2, false)::MemoryRef{Any} X │ Base.memoryrefset!(%39, %23, :not_atomic, false)::Main.SafeRef{String} ◌ └─── goto #11 ◌ 11 ─ %42 = $(Expr(:boundscheck, true))::Bool ◌ └─── goto #15 if not %42 ◌ 12 ─ %44 = Base.sub_int(1, 1)::Int64 ◌ │ %45 = Base.bitcast(Base.UInt, %44)::UInt64 X │ %46 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %47 = $(Expr(:boundscheck, true))::Bool X │ %48 = Base.getfield(%46, 1, %47)::Int64 ◌ │ %49 = Base.bitcast(Base.UInt, %48)::UInt64 ◌ │ %50 = Base.ult_int(%45, %49)::Bool ◌ └─── goto #14 if not %50 ◌ 13 ─ goto #15 ◌ 14 ─ %53 = Core.tuple(1)::Tuple{Int64} ✓ │ invoke Base.throw_boundserror(%3::Vector{Any}, %53::Tuple{Int64})::Union{} ◌ └─── unreachable X 15 ┄ %56 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %57 = Base.memoryrefnew(%56, 1, false)::MemoryRef{Any} X │ %58 = Base.memoryrefget(%57, :not_atomic, false)::Any ◌ └─── goto #16 X 16 ─ %60 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %61 = $(Expr(:boundscheck, true))::Bool X │ %62 = Base.getfield(%60, 1, %61)::Int64 ↑′ │ %63 = Core.tuple(%58, %62)::Tuple{Any, Int64} ◌ └─── return %63
ReturnEscape
は%2
(SafeRef(s)
に対応)にのみ課せられますが、%4
(SafeRef(t)
に対応)には課せられません。これは、割り当てられた配列の次元と、すべてのarrayref
/arrayset
操作に関与するインデックスが定数情報として利用可能であり、EscapeAnalysis
が%6
が%2
にエイリアスされているが、決して%4
にエイリアスされないことを理解できるからです。このような場合、後続の最適化パスはBase.arrayref(true, %1, 1)::Any
を%2
(いわゆる「ロードフォワーディング」)に置き換え、最終的に配列%1
の割り当てを完全に排除することができます(いわゆる「スカラー置換」)。
オブジェクトフィールド分析と比較すると、オブジェクトフィールドへのアクセスは推論によって得られた型情報を使用して簡単に分析できますが、配列の次元は型情報としてエンコードされていないため、その情報を導き出すために追加の分析が必要です。EscapeAnalysis
は、まず割り当てられた配列の次元を分析するために追加の単純な線形スキャンを行い、その後メインの分析ルーチンを起動することで、次のエスケープ分析がそれらの配列に対する操作を正確に分析できるようにします。
しかし、そのような正確な「要素ごとの」エイリアス解析はしばしば困難です。本質的に、配列に固有の主な難しさは、配列の次元とインデックスがしばしば定数でないことです:
- ループはしばしばループ変数、非定数の配列インデックスを生成します。
- (ベクトルに特有)配列のサイズ変更は配列の次元を変更し、その定数性を無効にします。
具体的な例を挙げて、その困難について話し合いましょう。
以下の例では、EscapeAnalysis
が正確なエイリアス分析に失敗します。なぜなら、Base.arrayset(false, %4, %8, %6)::Vector{Any}
のインデックスが(自明に)定数ではないからです。特に、Any[nothing, nothing]
はループを形成し、そのループ内でarrayset
操作を呼び出します。このとき、%6
は制御フローに依存するϕノード値として表されます。その結果、ReturnEscape
は%23
(SafeRef(s)
に対応)と%25
(SafeRef(t)
に対応)の両方に課せられますが、理想的には%23
のみに課せられ、%25
には課せられないことを望んでいます。
julia> code_escapes((String,String)) do s, t ary = Any[nothing, nothing] ary[1] = SafeRef(s) ary[2] = SafeRef(t) return ary[1], length(ary) end
#15(X s::String, X t::String) in Main at REPL[1]:2 X 1 ── %1 = $(Expr(:foreigncall, :(:jl_alloc_genericmemory), Ref{Memory{Any}}, svec(Any, Int64), 0, :(:ccall), Memory{Any}, 2, 2))::Memory{Any} X │ %2 = Core.memoryrefnew(%1)::MemoryRef{Any} X └─── %3 = %new(Vector{Any}, %2, (2,))::Vector{Any} ◌ 2 ┄─ %4 = φ (#1 => 1, #6 => %14)::Int64 ◌ │ %5 = φ (#1 => 1, #6 => %15)::Int64 X │ %6 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %7 = Base.memoryrefnew(%6, %4, false)::MemoryRef{Any} ◌ │ Base.memoryrefset!(%7, nothing, :not_atomic, false)::Nothing ◌ │ %9 = (%5 === 2)::Bool ◌ └─── goto #4 if not %9 ◌ 3 ── goto #5 ◌ 4 ── %12 = Base.add_int(%5, 1)::Int64 ◌ └─── goto #5 ◌ 5 ┄─ %14 = φ (#4 => %12)::Int64 ◌ │ %15 = φ (#4 => %12)::Int64 ◌ │ %16 = φ (#3 => true, #4 => false)::Bool ◌ │ %17 = Base.not_int(%16)::Bool ◌ └─── goto #7 if not %17 ◌ 6 ── goto #2 ◌ 7 ── goto #8 X 8 ── %21 = %new(Main.SafeRef{String}, _2)::Main.SafeRef{String} ◌ │ %22 = $(Expr(:boundscheck, true))::Bool ◌ └─── goto #12 if not %22 ◌ 9 ── %24 = Base.sub_int(1, 1)::Int64 ◌ │ %25 = Base.bitcast(Base.UInt, %24)::UInt64 X │ %26 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %27 = $(Expr(:boundscheck, true))::Bool X │ %28 = Base.getfield(%26, 1, %27)::Int64 ◌ │ %29 = Base.bitcast(Base.UInt, %28)::UInt64 ◌ │ %30 = Base.ult_int(%25, %29)::Bool ◌ └─── goto #11 if not %30 ◌ 10 ─ goto #12 ◌ 11 ─ %33 = Core.tuple(1)::Tuple{Int64} ✓ │ invoke Base.throw_boundserror(%3::Vector{Any}, %33::Tuple{Int64})::Union{} ◌ └─── unreachable X 12 ┄ %36 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %37 = Base.memoryrefnew(%36, 1, false)::MemoryRef{Any} X │ Base.memoryrefset!(%37, %21, :not_atomic, false)::Main.SafeRef{String} ◌ └─── goto #13 X 13 ─ %40 = %new(Main.SafeRef{String}, _3)::Main.SafeRef{String} ◌ │ %41 = $(Expr(:boundscheck, true))::Bool ◌ └─── goto #17 if not %41 ◌ 14 ─ %43 = Base.sub_int(2, 1)::Int64 ◌ │ %44 = Base.bitcast(Base.UInt, %43)::UInt64 X │ %45 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %46 = $(Expr(:boundscheck, true))::Bool X │ %47 = Base.getfield(%45, 1, %46)::Int64 ◌ │ %48 = Base.bitcast(Base.UInt, %47)::UInt64 ◌ │ %49 = Base.ult_int(%44, %48)::Bool ◌ └─── goto #16 if not %49 ◌ 15 ─ goto #17 ◌ 16 ─ %52 = Core.tuple(2)::Tuple{Int64} ✓ │ invoke Base.throw_boundserror(%3::Vector{Any}, %52::Tuple{Int64})::Union{} ◌ └─── unreachable X 17 ┄ %55 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %56 = Base.memoryrefnew(%55, 2, false)::MemoryRef{Any} X │ Base.memoryrefset!(%56, %40, :not_atomic, false)::Main.SafeRef{String} ◌ └─── goto #18 ◌ 18 ─ %59 = $(Expr(:boundscheck, true))::Bool ◌ └─── goto #22 if not %59 ◌ 19 ─ %61 = Base.sub_int(1, 1)::Int64 ◌ │ %62 = Base.bitcast(Base.UInt, %61)::UInt64 X │ %63 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %64 = $(Expr(:boundscheck, true))::Bool X │ %65 = Base.getfield(%63, 1, %64)::Int64 ◌ │ %66 = Base.bitcast(Base.UInt, %65)::UInt64 ◌ │ %67 = Base.ult_int(%62, %66)::Bool ◌ └─── goto #21 if not %67 ◌ 20 ─ goto #22 ◌ 21 ─ %70 = Core.tuple(1)::Tuple{Int64} ✓ │ invoke Base.throw_boundserror(%3::Vector{Any}, %70::Tuple{Int64})::Union{} ◌ └─── unreachable X 22 ┄ %73 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %74 = Base.memoryrefnew(%73, 1, false)::MemoryRef{Any} X │ %75 = Base.memoryrefget(%74, :not_atomic, false)::Any ◌ └─── goto #23 X 23 ─ %77 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %78 = $(Expr(:boundscheck, true))::Bool X │ %79 = Base.getfield(%77, 1, %78)::Int64 ↑′ │ %80 = Core.tuple(%75, %79)::Tuple{Any, Int64} ◌ └─── return %80
次の例は、ベクターのサイズ変更が正確なエイリアス分析を困難にする様子を示しています。基本的な難しさは、割り当てられた配列 %1
の次元が最初に 0
に初期化されることですが、その後の2つの :jl_array_grow_end
呼び出しによって変更されることです。現在、EscapeAnalysis
は、配列のサイズ変更操作に遭遇すると正確なエイリアス分析を単純に諦めてしまい、そのため %2
(SafeRef(s)
に対応)と %20
(SafeRef(t)
に対応)の両方に ReturnEscape
が課せられます。
julia> code_escapes((String,String)) do s, t ary = Any[] push!(ary, SafeRef(s)) push!(ary, SafeRef(t)) ary[1], length(ary) end
#17(X s::String, X t::String) in Main at REPL[1]:2 X 1 ── %1 = Core.getproperty(Memory{Any}, :instance)::Memory{Any} X │ %2 = invoke Core.memoryref(%1::Memory{Any})::MemoryRef{Any} X │ %3 = %new(Vector{Any}, %2, (0,))::Vector{Any} X │ %4 = %new(Main.SafeRef{String}, _2)::Main.SafeRef{String} X │ %5 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %6 = Base.getfield(%5, :mem)::Memory{Any} X │ %7 = Base.getfield(%6, :length)::Int64 X │ %8 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %9 = $(Expr(:boundscheck, true))::Bool X │ %10 = Base.getfield(%8, 1, %9)::Int64 ◌ │ %11 = Base.add_int(%10, 1)::Int64 ◌ │ %12 = Base.memoryrefoffset(%5)::Int64 X │ %13 = Core.tuple(%11)::Tuple{Int64} ◌ │ Base.setfield!(%3, :size, %13)::Tuple{Int64} ◌ │ %15 = Base.add_int(%12, %11)::Int64 ◌ │ %16 = Base.sub_int(%15, 1)::Int64 ◌ │ %17 = Base.slt_int(%7, %16)::Bool ◌ └─── goto #3 if not %17 X 2 ── %19 = %new(Base.var"#133#134"{Vector{Any}, Int64, Int64, Int64, Int64, Int64, Memory{Any}, MemoryRef{Any}}, %3, %16, %12, %11, %10, %7, %6, %5)::Base.var"#133#134"{Vector{Any}, Int64, Int64, Int64, Int64, Int64, Memory{Any}, MemoryRef{Any}} ✓ └─── invoke %19()::MemoryRef{Any} ◌ 3 ┄─ goto #4 X 4 ── %22 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %23 = $(Expr(:boundscheck, true))::Bool X │ %24 = Base.getfield(%22, 1, %23)::Int64 X │ %25 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %26 = Base.memoryrefnew(%25, %24, false)::MemoryRef{Any} X │ Base.memoryrefset!(%26, %4, :not_atomic, false)::Main.SafeRef{String} ◌ └─── goto #5 X 5 ── %29 = %new(Main.SafeRef{String}, _3)::Main.SafeRef{String} X │ %30 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %31 = Base.getfield(%30, :mem)::Memory{Any} X │ %32 = Base.getfield(%31, :length)::Int64 X │ %33 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %34 = $(Expr(:boundscheck, true))::Bool X │ %35 = Base.getfield(%33, 1, %34)::Int64 ◌ │ %36 = Base.add_int(%35, 1)::Int64 ◌ │ %37 = Base.memoryrefoffset(%30)::Int64 X │ %38 = Core.tuple(%36)::Tuple{Int64} ◌ │ Base.setfield!(%3, :size, %38)::Tuple{Int64} ◌ │ %40 = Base.add_int(%37, %36)::Int64 ◌ │ %41 = Base.sub_int(%40, 1)::Int64 ◌ │ %42 = Base.slt_int(%32, %41)::Bool ◌ └─── goto #7 if not %42 X 6 ── %44 = %new(Base.var"#133#134"{Vector{Any}, Int64, Int64, Int64, Int64, Int64, Memory{Any}, MemoryRef{Any}}, %3, %41, %37, %36, %35, %32, %31, %30)::Base.var"#133#134"{Vector{Any}, Int64, Int64, Int64, Int64, Int64, Memory{Any}, MemoryRef{Any}} ✓ └─── invoke %44()::MemoryRef{Any} ◌ 7 ┄─ goto #8 X 8 ── %47 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %48 = $(Expr(:boundscheck, true))::Bool X │ %49 = Base.getfield(%47, 1, %48)::Int64 X │ %50 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %51 = Base.memoryrefnew(%50, %49, false)::MemoryRef{Any} X │ Base.memoryrefset!(%51, %29, :not_atomic, false)::Main.SafeRef{String} ◌ └─── goto #9 ◌ 9 ── %54 = $(Expr(:boundscheck, true))::Bool ◌ └─── goto #13 if not %54 ◌ 10 ─ %56 = Base.sub_int(1, 1)::Int64 ◌ │ %57 = Base.bitcast(Base.UInt, %56)::UInt64 X │ %58 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %59 = $(Expr(:boundscheck, true))::Bool X │ %60 = Base.getfield(%58, 1, %59)::Int64 ◌ │ %61 = Base.bitcast(Base.UInt, %60)::UInt64 ◌ │ %62 = Base.ult_int(%57, %61)::Bool ◌ └─── goto #12 if not %62 ◌ 11 ─ goto #13 ◌ 12 ─ %65 = Core.tuple(1)::Tuple{Int64} ✓ │ invoke Base.throw_boundserror(%3::Vector{Any}, %65::Tuple{Int64})::Union{} ◌ └─── unreachable X 13 ┄ %68 = Base.getfield(%3, :ref)::MemoryRef{Any} X │ %69 = Base.memoryrefnew(%68, 1, false)::MemoryRef{Any} X │ %70 = Base.memoryrefget(%69, :not_atomic, false)::Any ◌ └─── goto #14 X 14 ─ %72 = Base.getfield(%3, :size)::Tuple{Int64} ◌ │ %73 = $(Expr(:boundscheck, true))::Bool X │ %74 = Base.getfield(%72, 1, %73)::Int64 ↑′ │ %75 = Core.tuple(%70, %74)::Tuple{Any, Int64} ◌ └─── return %75
これらの課題に対処するためには、推論が配列の次元を認識し、フローに敏感な方法で配列の次元を伝播させる必要があります[ArrayDimension]。また、ループ変数の値の良い表現を考案する必要があります。
EscapeAnalysis
は、配列の次元やインデックスが明らかに定数でない場合に、正確なインデックス情報を追跡しないより不正確な分析に迅速に切り替わります。この切り替えは、データフロー分析フレームワークにおける EscapeInfo.AliasInfo
プロパティのラティス結合操作として自然に実装できます。
Exception Handling
EscapeAnalysis
が例外を介した可能性のあるエスケープをどのように処理するかについても言及する価値があります。単純に考えると、対応するtry
ブロックでスローされる可能性のあるすべての値に対して:the_exception
オブジェクトに課せられたエスケープ情報を伝播させるだけで十分なように思えます。しかし、実際には、Base.current_exceptions
やrethrow
など、Juliaで例外オブジェクトにアクセスするための他のいくつかの方法があります。たとえば、エスケープ分析は、以下の例でr
の潜在的なエスケープを考慮する必要があります。
julia> const GR = Ref{Any}();
julia> @noinline function rethrow_escape!() try rethrow() catch err GR[] = err end end;
julia> get′(x) = isassigned(x) ? x[] : throw(x);
julia> code_escapes() do r = Ref{String}() local t try t = get′(r) catch err t = typeof(err) # `err` (which `r` aliases to) doesn't escape here rethrow_escape!() # but `r` escapes here end return t end
#19() in Main at REPL[4]:2 X 1 ─ %1 = %new(Base.RefValue{String})::Base.RefValue{String} ◌ 2 ─ %2 = enter #8 ◌ 3 ─ %3 = Base.isdefined(%1, :x)::Bool ◌ └── goto #5 if not %3 X 4 ─ %5 = Base.getfield(%1, :x)::String ◌ └── goto #6 ◌ 5 ─ Main.throw(%1)::Union{} ◌ └── unreachable ◌ 6 ─ $(Expr(:leave, :(%2))) ◌ 7 ─ goto #9 ✓ 8 ┄ %11 = $(Expr(:the_exception))::Any X │ %12 = Main.typeof(%11)::DataType ✓ │ invoke Main.rethrow_escape!()::Any ◌ └── $(Expr(:pop_exception, :(%2)))::Core.Const(nothing) X 9 ┄ %15 = φ (#7 => %5, #8 => %12)::Union{DataType, String} ◌ └── return %15
すべての既存の例外インターフェースを介した可能なエスケープについて正しく推論するには、グローバルな分析が必要です。現時点では、例外処理とエラーパスが通常それほどパフォーマンスに敏感である必要がないことを考慮して、最上位のエスケープ情報をすべての潜在的にスローされるオブジェクトに保守的に伝播させています。また、エラーパスの最適化は、レイテンシの理由から意図的に「最適化されていない」ことが多いため、非常に効果的でない可能性があります。
x::EscapeInfo
のx.ThrownEscape
プロパティは、x
が例外としてスローされる可能性のあるSSAステートメントを記録します。この情報を使用して、EscapeAnalysis
は、各try
領域でスローされる可能性のある例外に限って、例外を介した可能なエスケープを制限的に伝播させることができます。
julia> result = code_escapes((String,String)) do s1, s2 r1 = Ref(s1) r2 = Ref(s2) local ret try s1 = get′(r1) ret = sizeof(s1) catch err global GV = err # will definitely escape `r1` end s2 = get′(r2) # still `r2` doesn't escape fully return s2 end
#21(X s1::String, ↑ s2::String) in Main at REPL[1]:2 X 1 ── %1 = %new(Base.RefValue{String}, _2)::Base.RefValue{String} *′ └─── %2 = %new(Base.RefValue{String}, _3)::Base.RefValue{String} *′ 2 ── %3 = ϒ (%2)::Base.RefValue{String} ◌ └─── %4 = enter #8 ◌ 3 ── %5 = Base.isdefined(%1, :x)::Bool ◌ └─── goto #5 if not %5 X 4 ── Base.getfield(%1, :x)::String ◌ └─── goto #6 ◌ 5 ── Main.throw(%1)::Union{} ◌ └─── unreachable ◌ 6 ── $(Expr(:leave, :(%4))) ◌ 7 ── goto #11 *′ 8 ┄─ %13 = φᶜ (%3)::Base.RefValue{String} X └─── %14 = $(Expr(:the_exception))::Any ◌ 9 ── nothing::Nothing ◌ 10 ─ (Main.GV = %14)::Any ◌ └─── $(Expr(:pop_exception, :(%4)))::Core.Const(nothing) *′ 11 ┄ %18 = φ (#7 => %2, #10 => %13)::Base.RefValue{String} ◌ │ %19 = Base.isdefined(%18, :x)::Bool ◌ └─── goto #13 if not %19 ↑ 12 ─ %21 = Base.getfield(%18, :x)::String ◌ └─── goto #14 ◌ 13 ─ Main.throw(%18)::Union{} ◌ └─── unreachable ◌ 14 ─ return %21
Analysis Usage
analyze_escapes
は、SSA-IR要素のエスケープ情報を分析するためのエントリーポイントです。
最適化の多くは、SROA(sroa_pass!
)のように、インライン化パス(ssa_inlining_pass!
)によって手続き間呼び出しが解決され、呼び出し元が展開された最適化されたソースに適用されると、より効果的です。それに応じて、analyze_escapes
もインライン化後のIRを分析し、特定のメモリ関連の最適化に役立つエスケープ情報を収集することができます。
しかし、インライン化のような特定の最適化パスは制御フローを変更し、デッドコードを排除することができるため、エスケープ情報の手続き間の有効性を破る可能性があります。特に、手続き間で有効なエスケープ情報を収集するためには、インライン化前の中間表現(IR)を分析する必要があります。
この理由から、analyze_escapes
は任意のJuliaレベルの最適化ステージで IRCode
を分析でき、特に以下の2つのステージで使用されることが想定されています:
IPO EA
: プレインライン IR を分析して IPO 有効なエスケープ情報キャッシュを生成するLocal EA
: インライン後のIRを分析して、ローカルに有効なエスケープ情報を収集します。
IPO EA
によって導出されたエスケープ情報は、ArgEscapeCache
データ構造に変換され、グローバルにキャッシュされます。適切なget_escape_cache
コールバックをanalyze_escapes
に渡すことで、エスケープ分析は以前のIPO EA
によって導出されたインラインされていない呼び出し先のキャッシュされた手続き間情報を利用することで分析精度を向上させることができます。さらに興味深いことに、IPO EA
のエスケープ情報を型推論に使用することも有効であり、例えば、可変オブジェクトのConst
/PartialStruct
/MustAlias
を形成することで推論精度を向上させることができます。
Core.Compiler.EscapeAnalysis.analyze_escapes
— Functionanalyze_escapes(ir::IRCode, nargs::Int, get_escape_cache) -> estate::EscapeState
ir
内のエスケープ情報を分析します:
nargs
: 分析対象の呼び出しの実際の引数の数get_escape_cache(::MethodInstance) -> Union{Bool,ArgEscapeCache}
: キャッシュされた引数エスケープ情報を取得します
Core.Compiler.EscapeAnalysis.EscapeState
— Typeestate::EscapeState
引き延ばされた格子は、引数とSSA値をEscapeInfo
として表現されたエスケープ情報にマッピングします。SSA IR要素x
に課せられたエスケープ情報は、estate[x]
を使用して取得できます。
Core.Compiler.EscapeAnalysis.EscapeInfo
— Typex::EscapeInfo
エスケープ情報のためのラティスで、以下のプロパティを保持します:
x.Analyzed::Bool
: ラティスの正式な部分ではなく、x
が分析されたかどうかを示しますx.ReturnEscape::Bool
:x
が戻り値を介して呼び出し元にエスケープできることを示しますx.ThrownEscape::BitSet
:x
が例外としてスローされる可能性のあるSSAステートメント番号を記録します:isempty(x.ThrownEscape)
:x
はこのコールフレームで決してスローされません(ボトム)pc ∈ x.ThrownEscape
:x
はpc
のSSAステートメントでスローされる可能性があります-1 ∈ x.ThrownEscape
:x
はこのコールフレームの任意のポイントでスローされる可能性があります(トップ)
この情報は、
escape_exception!
によって例外を介して潜在的なエスケープを伝播するために使用されます。x.AliasInfo::Union{Bool,IndexableFields,IndexableElements,Unindexable}
:x
のフィールドまたは配列要素にエイリアスされる可能性のあるすべての値を保持します:x.AliasInfo === false
は、x
のフィールド/要素がまだ分析されていないことを示しますx.AliasInfo === true
は、x
のフィールド/要素が分析できないことを示します。例えば、x
の型が不明であるか、具体的でないため、そのフィールド/要素を正確に知ることができませんx.AliasInfo::IndexableFields
は、オブジェクトx
のフィールドにエイリアスされる可能性のあるすべての値を正確なインデックス情報と共に記録しますx.AliasInfo::IndexableElements
は、配列x
の要素にエイリアスされる可能性のあるすべての値を正確なインデックス情報と共に記録しますx.AliasInfo::Unindexable
は、正確なインデックス情報なしにx
のフィールド/要素にエイリアスされる可能性のあるすべての値を記録します
x.Liveness::BitSet
:x
が生きているべきSSAステートメント番号を記録します。例えば、呼び出し引数として使用される、呼び出し元に返される、または:foreigncall
のために保持されるべきです:isempty(x.Liveness)
:x
はこのコールフレームで決して使用されません(ボトム)0 ∈ x.Liveness
は、現在分析中のコールフレームの呼び出し引数であることを示す特別な意味を持ちます(したがって、呼び出し元からすぐに見える)。pc ∈ x.Liveness
:x
はpc
のSSAステートメントで使用される可能性があります-1 ∈ x.Liveness
:x
はこのコールフレームの任意のポイントで使用される可能性があります(トップ)
一般的な EscapeInfo
を作成するためのユーティリティコンストラクタがあります。例えば、
NoEscape()
: このラティスのボトム(類似)要素で、どこにもエスケープしないことを意味しますAllEscape()
: このラティスの最上位要素で、どこにでもエスケープすることを意味します
analyze_escapes
は、これらの要素をボトムからトップに遷移させます。これは、Julia のネイティブ型推論ルーチンと同じ方向です。抽象状態はボトム(類似)要素で初期化されます:
- 呼び出し引数は
ArgEscape()
として初期化され、そのLiveness
プロパティには0
が含まれ、呼び出し引数として渡され、呼び出し元からすぐに見えることを示します - 他の状態は
NotAnalyzed()
として初期化されます。これはNoEscape
よりもわずかに低い特別なラティス要素ですが、同時にまだ分析されていないこと以外の意味を持ちません(したがって、正式にはラティスの一部ではありません)
- LatticeDesignOur type inference implementation takes the alternative approach, where each lattice property is represented by a special lattice element type object. It turns out that it started to complicate implementations of the lattice operations mainly because it often requires conversion rules between each lattice element type object. And we are working on overhauling our type inference lattice implementation with
EscapeInfo
-like lattice design. - MM02A Graph-Free approach to Data-Flow Analysis. Markas Mohnen, 2002, April. https://api.semanticscholar.org/CorpusID:28519618.
- BackandForthOur type inference algorithm in contrast is implemented as a forward analysis, because type information usually flows from "definition" to "usage" and it is more natural and effective to propagate such information in a forward way.
- DynamismIn some cases, however, object fields can't be analyzed precisely. For example, object may escape to somewhere
EscapeAnalysis
can't account for possible memory effects on it, or fields of the objects simply can't be known because of the lack of type information. In such casesAliasInfo
property is raised to the topmost element within its own lattice order, and it causes succeeding field analysis to be conservative and escape information imposed on fields of an unanalyzable object to be propagated to the object itself. - JVM05Escape Analysis in the Context of Dynamic Compilation and Deoptimization. Thomas Kotzmann and Hanspeter Mössenböck, 2005, June. https://dl.acm.org/doi/10.1145/1064979.1064996.
- ArrayDimensionOtherwise we will need yet another forward data-flow analysis on top of the escape analysis.