EscapeAnalysis

Core.Compiler.EscapeAnalysis, bir derleyici yardımcı modülüdür ve Julia's SSA-form IR yani IRCode'un kaçış bilgilerini analiz etmeyi amaçlamaktadır.

Bu kaçış analizi şunları hedeflemektedir:

  • Julia'nın yüksek seviyeli anlamsal özelliklerinden yararlanarak, özellikle prosedürler arası çağrılar aracılığıyla kaçışlar ve takaslar hakkında düşünün.
  • çeşitli optimizasyonlar için kullanılabilecek kadar çok yönlü olun, bunlar arasında alias-aware SROA, early finalize insertion, copy-free ImmutableArray construction, değiştirilebilir nesnelerin yığın tahsisi ve daha fazlası.
  • basit bir uygulama gerçekleştirin, tamamen geriye dönük veri akışı analizi uygulamasına dayalı olarak ve ortogonal ızgara özelliklerini birleştiren yeni bir ızgara tasarımı ile

Try it out!

Kaçış analizini denemek için, test ve hata ayıklama amaçları için code_escapes ve @code_escapes kolaylık girişlerini tanımlayan EAUtils.jl yardımcı betiğini yükleyebilirsiniz:

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))::Any9 ── nothing::Nothing10 ─ (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

Her çağrı argümanı ve SSA ifadelerinin yanındaki semboller aşağıdaki anlamları temsil eder:

  • (düz): bu değer analiz edilmez çünkü bunun kaçış bilgisi her halükarda kullanılmayacaktır (örneğin nesne isbitstype olduğunda)
  • (yeşil veya camgöbeği): bu değer asla kaçmaz (has_no_escape(result.state[x]) geçerlidir), arg kaçışı varsa mavi renkte (has_arg_escape(result.state[x]) geçerlidir)
  • (mavi veya sarı): bu değer, çağırana dönüş ile kaçabilir (has_return_escape(result.state[x]) geçerlidir), ayrıca işlenmemiş fırlatma kaçışı varsa sarı renkte (has_thrown_escape(result.state[x]) geçerlidir)
  • X (kırmızı): bu değer, kaçış analizinin hakkında bir şeyler düşünemediği bir yere kaçabilir, örneğin global bir belleğe kaçış (has_all_escape(result.state[x]) geçerlidir)
  • * (kalın): bu değerin kaçış durumu, EscapeInfo kısmi sıralamasında ReturnEscape ve AllEscape arasında yer almaktadır, eğer işlenmemiş bir fırlatma kaçışı varsa sarı renkte gösterilir (has_thrown_escape(result.state[x]) geçerlidir)
  • : bu değer, AliasInfo özelliğinde ek nesne alanı / dizi elemanı bilgisi taşır.

Her arama argümanının ve SSA değerinin kaçış bilgisi programatik olarak aşağıdaki gibi incelenebilir:

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 olarak uygulanmıştır ve x::EscapeInfo latticesinde çalışır ve aşağıdaki özelliklerden oluşur:

  • x.Analyzed::Bool: resmi olarak lattice'in bir parçası değildir, sadece x'in analiz edilip edilmediğini gösterir.
  • x.ReturnEscape::BitSet: x'in çağırana dönüş yoluyla kaçabileceği SSA ifadelerini kaydeder.
  • x.ThrownEscape::BitSet: x'in istisna olarak atılabileceği SSA ifadelerini kaydeder (aşağıda açıklanan exception handling için kullanılır)
  • x.AliasInfo: x'in alanlarına veya dizi elemanlarına takma ad olarak atanabilecek tüm olası değerleri saklar (aşağıda açıklanan alias analysis için kullanılır)
  • x.ArgEscape::Int (henüz uygulanmadı): argüman(lar) üzerinde setfield! ile çağırana kaçacağını belirtir.

Bu özellikler, bir giriş programının sonlu sayıda ifadeye sahip olduğu garantisiyle, sonlu bir yüksekliğe sahip kısmi bir ızgara oluşturmak için birleştirilebilir; bu, Julia'nın anlamsal özellikleriyle sağlanmaktadır. Bu ızgara tasarımının akıllıca kısmı, her ızgara özelliğini ayrı ayrı ele alarak ızgara işlemlerinin daha basit bir şekilde uygulanmasını sağlamasıdır[LatticeDesign].

Backward Escape Propagation

Bu kaçış analizi uygulaması, makalede tanımlanan veri akışı algoritmasına dayanmaktadır[MM02]. Analiz, EscapeInfo ızgarasında çalışır ve her ızgara elemanının, analiz edilecek kalan SSA ifadelerine karşılık gelen program sayaçlarını içeren (kavramsal) bir çalışma kümesini koruyarak, alt seviyeden üst seviyeye geçiş yaparak sabit bir noktaya ulaşana kadar ızgara elemanlarını birleştirir. Analiz, her argümanın ve SSA ifadesinin EscapeInfo'sunu izleyen tek bir küresel durumu yönetir, ancak bazı akış duyarlılığının, gerektiğinde akış duyarlılığını düşünmek için daha sonra baskın analizle birleştirilebilecek EscapeInfo'nun ReturnEscape özelliğinde kaydedilen program sayaçları olarak kodlandığını da unutmayın.

Bu kaçış analizinin bir ayırt edici tasarımı, tamamen geriye dönük olmasıdır; yani kaçış bilgisi kullanımlardan tanımlara akar. Örneğin, aşağıdaki kod parçasında, EA önce return %1 ifadesini analiz eder ve %1 (karşılık gelen obj) üzerinde ReturnEscape uygular, ardından %1 = %new(Base.RefValue{String, _2})) ifadesini analiz eder ve %1 üzerinde uygulanan ReturnEscape'i çağrı argümanı _2 (karşılık gelen s) üzerine yayar.

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

Buradaki ana gözlem, bu geriye dönük analizinin kaçış bilgilerinin kullanım-tanım zinciri boyunca doğal bir şekilde akmasına izin vermesidir, kontrol akışının aksine[BackandForth]. Sonuç olarak, bu şema kaçış analizinin basit bir uygulamasını mümkün kılar; örneğin, PhiNode gibi bir yapı, bir PhiNode üzerindeki kaçış bilgilerini onun öncül değerlerine yayarak basit bir şekilde işlenebilir:

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]:21 ─      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, belirli bir doğrulukla nesne alanlarına uygulanan kaçışları değerlendirmek için geriye dönük bir alan analizi uygular ve x::EscapeInfo'nun x.AliasInfo özelliği bu amaçla mevcuttur. Bu özellik, "kullanım" yerlerinde x'in alanlarına takas edilebilecek tüm olası değerleri kaydeder ve ardından bu kaydedilen değerlerin kaçış bilgileri, "tanım" yerlerinde gerçek alan değerlerine aktarılır. Daha spesifik olarak, analiz, getfield çağrısını analiz ederek bir nesne alanına takas edilebilecek bir değeri kaydeder ve ardından %new(...) ifadesini veya setfield! çağrısını analiz ederken bu değerin kaçış bilgisini alana aktarır[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]:21 ─     return _2

Yukarıdaki örnekte, %3 (karşılık gelen v) üzerinde uygulanan ReturnEscape, doğrudan %1'e (karşılık gelen obj) aktarılmamaktadır; bunun yerine ReturnEscape yalnızca _2'ye (karşılık gelen s) aktarılmaktadır. Burada %3, %1'in AliasInfo özelliğinde kaydedilir çünkü %1'in ilk alanına alias olabilmektedir ve ardından Base.setfield!(%1, :x, _2)::String analiz edilirken, bu kaçış bilgisi _2'ye aktarılır ancak %1'e aktarılmaz.

EscapeAnalysis, getfield-%new/setfield! zincirinde hangi IR öğelerinin takas edilebileceğini izler, böylece nesne alanlarının kaçışlarını analiz edebilir, ancak aslında bu takas analizi diğer IR öğelerini de ele alacak şekilde genelleştirilmelidir. Bunun nedeni, Julia IR'de aynı nesnenin bazen farklı IR öğeleriyle temsil edilmesidir ve bu nedenle aslında aynı nesneyi temsil edebilen bu farklı IR öğelerinin aynı kaçış bilgilerini paylaşmasını sağlamalıyız. Operandi olarak aynı nesneyi döndüren IR öğeleri, örneğin PiNode ve typeassert, bu IR düzeyindeki takasları oluşturabilir ve bu nedenle bu tür takas edilen değerler üzerindeki kaçış bilgisi aralarında paylaşılmalıdır. Daha ilginç bir şekilde, PhiNode üzerindeki mutasyonlar hakkında doğru bir şekilde düşünmek için de gereklidir. Aşağıdaki örneği ele alalım:

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]:21 ─      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 ve ϕ2 = %6 takma adlandırılmıştır ve bu nedenle %8 = Base.getfield(%6, :x)::String (karşılık gelen y = ϕ1[]) üzerinde uygulanan ReturnEscape, Base.setfield!(%5, :x, _3)::String (karşılık gelen ϕ2[] = x) üzerine yayılmalıdır. Bu tür kaçış bilgilerinin doğru bir şekilde yayılabilmesi için, analiz, ϕ1 ve ϕ2'nin öncüllerinin de takma adlandırılabileceğini ve kaçış bilgilerini eşitlemesi gerektiğini tanımalıdır.

Böyle bir takma ad bilgisiyle ilgili ilginç bir özellik, "kullanım" noktasında bilinmemesi, yalnızca "tanım" noktasında türetilebilmesidir (çünkü takma ad, kavramsal olarak atama ile eşdeğerdir) ve bu nedenle geriye dönük bir analize doğal olarak uymaz. İlgili değerler arasında kaçış bilgilerini verimli bir şekilde yaymak için, EscapeAnalysis.jl, eski bir JVM makalesinde açıklanan kaçış analizi algoritısından ilham alan bir yaklaşım kullanır[JVM05]. Yani, kaçış lattice elemanlarını yönetmenin yanı sıra, analiz ayrıca "eşit"-takma ad kümesini, takma adlı argümanlar ve SSA ifadelerinin ayrık bir kümesini de sürdürmektedir. Takma ad kümesi, birbirine takma ad olabilen değerleri yönetir ve bu tür takma adlı değerlerden herhangi birine uygulanan kaçış bilgisinin aralarında eşitlenmesine olanak tanır.

Array Analysis

Yukarıda tanımlanan nesne alanları için takma ad analizi, dizi işlemlerini analiz etmek için de genelleştirilebilir. EscapeAnalysis, arrayref-arrayset kullanım-tanım zinciri aracılığıyla kaçışları iletecek şekilde çeşitli ilkel dizi işlemleri için işlemler uygular ve tahsis edilen dizilerin çok muhafazakar bir şekilde kaçmasına izin vermez:

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

Yukarıdaki örnekte EscapeAnalysis, %20 ve %2 (tahsis edilen nesne SafeRef(s) ile ilgili) arrayset-arrayref zinciri aracılığıyla aliaslandığını anlar ve bunlar üzerinde ReturnEscape uygular, ancak tahsis edilen dizi %1 ( ary ile ilgili) üzerinde bunu uygulamaz. EscapeAnalysis yine de ary üzerinde ThrownEscape uygular çünkü BoundsError aracılığıyla potansiyel kaçışları da hesaba katması gerekir, ancak bu tür işlenmemiş ThrownEscape'lerin genellikle ary tahsisini optimize ederken göz ardı edilebileceğini de belirtmek gerekir.

Ayrıca, dizi indeks bilgisi ve dizi boyutları kesin olarak bilindiğinde, EscapeAnalysis, arrayref-arrayset zinciri aracılığıyla "her bir eleman" aliasing hakkında bile düşünme yeteneğine sahiptir; çünkü EscapeAnalysis, nesneler için "her bir alan" alias analizini gerçekleştirir:

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

Not edin ki ReturnEscape yalnızca %2 (karşılık gelen SafeRef(s)) üzerinde uygulanmaktadır, ancak %4 (karşılık gelen SafeRef(t)) üzerinde uygulanmamaktadır. Bunun nedeni, tahsis edilen dizinin boyutunun ve tüm arrayref/arrayset işlemleriyle ilgili indekslerin sabit bilgi olarak mevcut olması ve EscapeAnalysis'ın %6'nın %2 ile alias olduğunu, ancak asla %4 ile alias olamayacağını anlayabilmesidir. Bu tür bir durumda, sonraki optimizasyon geçişleri Base.arrayref(true, %1, 1)::Any'yi %2 ile (diğer bir deyişle "yükleme-ileri alma") değiştirebilecek ve nihayetinde dizi %1'in tahsisatını tamamen ortadan kaldırabilecektir (diğer bir deyişle "skalar değiştirme").

Nesne alanı analizine kıyasla, nesne alanına erişim, çıkarım yoluyla elde edilen tür bilgisi kullanılarak basit bir şekilde analiz edilebilirken, dizi boyutu tür bilgisi olarak kodlanmamıştır ve bu nedenle bu bilgiyi elde etmek için ek bir analize ihtiyaç vardır. EscapeAnalysis şu anda, tahsis edilen dizilerin boyutlarını analiz etmek için ana analiz rutinini başlatmadan önce ek bir basit doğrusal tarama yapar, böylece sonraki kaçış analizi bu diziler üzerindeki işlemleri kesin bir şekilde analiz edebilir.

Ancak, bu tür hassas "öğe başına" takma ad analizi genellikle zordur. Temelde, dizinin doğasında gelen ana zorluk, dizi boyutunun ve indeksinin genellikle sabit olmamasıdır:

  • döngü sıklıkla döngü-variant, sabit olmayan dizi indeksleri üretir
  • (vektörlere özgü) dizi yeniden boyutlandırma, dizi boyutunu değiştirir ve sabitliğini geçersiz kılar.

Somut örneklerle bu zorlukları tartışalım.

Aşağıdaki örnekte, EscapeAnalysis kesin alias analizini başarısız kılıyor çünkü Base.arrayset(false, %4, %8, %6)::Vector{Any} içindeki indeks (basitçe) sabit değildir. Özellikle Any[nothing, nothing] bir döngü oluşturur ve bu arrayset işlemini bir döngü içinde çağırır, burada %6 bir ϕ-düğüm değeri olarak temsil edilir (değeri kontrol akışına bağlıdır). Sonuç olarak, ReturnEscape hem %23 ( SafeRef(s) ile ilgili) hem de %25 ( SafeRef(t) ile ilgili) üzerinde uygulanır, ancak ideal olarak bunun yalnızca %23 üzerinde uygulanmasını istiyoruz, %25 üzerinde değil:

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

Sonraki örnek, vektör boyutlandırmanın kesin takma ad analizini nasıl zorlaştırdığını göstermektedir. Temel zorluk, tahsis edilen dizi %1'in ilk olarak 0 olarak başlatılmasıdır, ancak daha sonra iki :jl_array_grow_end çağrısıyla değişir. EscapeAnalysis şu anda herhangi bir dizi boyutlandırma işlemiyle karşılaştığında kesin takma ad analizinden vazgeçmektedir ve bu nedenle %2 ( SafeRef(s) ile ilgili) ve %20 ( SafeRef(t) ile ilgili) üzerinde ReturnEscape uygulanmaktadır:

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

Bu zorlukları aşmak için, çıkarımın dizi boyutlarının farkında olması ve dizi boyutlarını akışa duyarlı bir şekilde yayması[ArrayDimension] gerektiği gibi, döngü değişkeni değerlerinin güzel bir temsilini de bulmamız gerekiyor.

EscapeAnalysis şu anda dizi boyutları veya indeksleri basit bir şekilde sabit olmadığında, kesin indeks bilgilerini takip etmeyen daha belirsiz bir analize hızlıca geçiş yapar. Bu geçiş, veri akışı analizi çerçevesinde EscapeInfo.AliasInfo özelliğinin bir ızgara birleşim işlemi olarak doğal bir şekilde uygulanabilir.

Exception Handling

EscapeAnalysis istisnası yoluyla olası kaçışları nasıl ele aldığını belirtmek de önemlidir. Naif bir şekilde, try bloğunda atılabilecek tüm değerlere :the_exception nesnesine uygulanan kaçış bilgilerini yaymak yeterli gibi görünmektedir. Ancak, Julia'da Base.current_exceptions ve rethrow gibi istisna nesnesine erişmenin birkaç başka yolu vardır. Örneğin, aşağıdaki örnekte r'nin potansiyel kaçışını hesaba katmak için kaçış analizi gereklidir:

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

Tüm olası kaçışları mevcut istisna arayüzleri aracılığıyla doğru bir şekilde değerlendirmek için küresel bir analiz gereklidir. Şu anda, üst düzey kaçış bilgilerini, potansiyel olarak fırlatılan tüm nesnelere temkinli bir şekilde iletmekteyiz, çünkü böyle bir ek analiz yapmak, istisna işleme ve hata yollarının genellikle çok performans duyarlı olması gerekmediği göz önüne alındığında, yapılmaya değer olmayabilir ve ayrıca hata yollarının optimizasyonları genellikle etkisiz olabilir, çünkü genellikle gecikme nedenleriyle "optimize edilmemiş" olarak bile tasarlanmışlardır.

x::EscapeInfo'nin x.ThrownEscape özelliği, x'in bir istisna olarak atılabileceği SSA ifadelerini kaydeder. Bu bilgiyi kullanarak EscapeAnalysis, her try bölgesinde atılabilecek olanlarla sınırlı olarak istisnalar aracılığıyla olası kaçışları yayabilir:

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))::Any9 ──       nothing::Nothing10 ─       (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 öğelerinin kaçış bilgilerini analiz etmek için giriş noktasıdır.

Çoğu optimizasyon, SROA (sroa_pass!) gibi, inter-prosedürel çağrıları çözerek ve çağrılan kaynakları genişleterek inlining geçişi (ssa_inlining_pass!) tarafından basitleştirilmiş optimize edilmiş bir kaynağa uygulandığında daha etkilidir. Buna göre, analyze_escapes de inlining sonrası IR'yi analiz edebilir ve belirli bellekle ilgili optimizasyonlar için yararlı olan kaçış bilgilerini toplayabilir.

Ancak, inlining gibi belirli optimizasyon geçişleri kontrol akışlarını değiştirebileceği ve ölü kodu ortadan kaldırabileceği için, kaçış bilgilerin inter-prosedürel geçerliliğini bozabilir. Özellikle, inter-prosedürel olarak geçerli kaçış bilgilerini toplamak için, bir ön-inlining IR'yi analiz etmemiz gerekiyor.

Bu nedenle, analyze_escapes herhangi bir Julia düzeyindeki optimizasyon aşamasında IRCode'u analiz edebilir ve özellikle aşağıdaki iki aşamada kullanılması beklenmektedir:

  • IPO EA: önceden satır içi IR'yi analiz ederek IPO-geçerli kaçış bilgisi önbelleği oluşturma
  • Yerel EA: yerel olarak geçerli kaçış bilgilerini toplamak için post-inlining IR'yi analiz et

IPO EA tarafından türetilen kaçış bilgisi ArgEscapeCache veri yapısına dönüştürülür ve küresel olarak önbelleğe alınır. Uygun bir get_escape_cache geri çağrısını analyze_escapes fonksiyonuna geçirerek, kaçış analizi, önceki IPO EA tarafından türetilen, iç içe geçmiş çağrılara ait önbelleğe alınmış prosedürel bilgileri kullanarak analiz doğruluğunu artırabilir. Daha ilginç bir şekilde, IPO EA kaçış bilgilerini tür çıkarımı için kullanmak da geçerlidir; örneğin, çıkarım doğruluğu, değişken nesnenin Const/PartialStruct/MustAlias oluşturulmasıyla artırılabilir.

Core.Compiler.EscapeAnalysis.analyze_escapesFunction
analyze_escapes(ir::IRCode, nargs::Int, get_escape_cache) -> estate::EscapeState

ir içindeki kaçış bilgilerini analiz eder:

  • nargs: analiz edilen çağrının gerçek argüman sayısı
  • get_escape_cache(::MethodInstance) -> Union{Bool,ArgEscapeCache}: önbelleğe alınmış argüman kaçış bilgilerini alır
source
Core.Compiler.EscapeAnalysis.EscapeInfoType
x::EscapeInfo

Kaçış bilgileri için bir ızgara, aşağıdaki özellikleri tutar:

  • x.Analyzed::Bool: ızgaranın resmi bir parçası değildir, yalnızca x'in analiz edilip edilmediğini gösterir

  • x.ReturnEscape::Bool: x'in geri dönüş yoluyla çağırana kaçabileceğini gösterir

  • x.ThrownEscape::BitSet: x'in istisna olarak atılabileceği SSA ifade numaralarını kaydeder:

    • isempty(x.ThrownEscape): x bu çağrı çerçevesinde asla atılmayacaktır (en alt)
    • pc ∈ x.ThrownEscape: x pc'deki SSA ifadesinde atılabilir
    • -1 ∈ x.ThrownEscape: x bu çağrı çerçevesinin herhangi bir noktasında atılabilir (en üst)

    Bu bilgi, istisna yoluyla potansiyel kaçışları yaymak için escape_exception! tarafından kullanılacaktır.

  • x.AliasInfo::Union{Bool,IndexableFields,IndexableElements,Unindexable}: x'in alanlarına veya dizi elemanlarına taklit edilebilecek tüm olası değerleri tutar:

    • x.AliasInfo === false x'in alanlarının/elemanlarının henüz analiz edilmediğini gösterir
    • x.AliasInfo === true x'in alanlarının/elemanlarının analiz edilemeyeceğini gösterir, örneğin x'in tipi bilinmiyor veya somut değil ve dolayısıyla alanlarının/elemanlarının kesin olarak bilinmesi mümkün değil
    • x.AliasInfo::IndexableFields kesin indeks bilgisi ile x nesnesinin alanlarına taklit edilebilecek tüm olası değerleri kaydeder
    • x.AliasInfo::IndexableElements kesin indeks bilgisi ile x dizisinin elemanlarına taklit edilebilecek tüm olası değerleri kaydeder
    • x.AliasInfo::Unindexable kesin indeks bilgisi olmadan x'in alanlarına/elemanlarına taklit edilebilecek tüm olası değerleri kaydeder
  • x.Liveness::BitSet: x'in canlı olması gereken SSA ifade numaralarını kaydeder, örneğin bir çağrı argümanı olarak kullanılmak, bir çağırana geri döndürülmek veya :foreigncall için korunmak üzere:

    • isempty(x.Liveness): x bu çağrı çerçevesinde asla kullanılmayacaktır (en alt)
    • 0 ∈ x.Liveness ayrıca, şu anda analiz edilen çağrı çerçevesinin bir çağrı argümanı olduğu özel anlamına gelir (ve dolayısıyla çağırandan hemen görünür).
    • pc ∈ x.Liveness: x pc'deki SSA ifadesinde kullanılabilir
    • -1 ∈ x.Liveness: x bu çağrı çerçevesinin herhangi bir noktasında kullanılabilir (en üst)

Ortak EscapeInfo'ları oluşturmak için yardımcı yapıcılar vardır, örneğin,

  • NoEscape(): bu ızgaranın en alt (-benzeri) elemanı, hiçbir yere kaçmayacağı anlamına gelir
  • AllEscape(): bu ızgaranın en üst elemanı, her yere kaçacağı anlamına gelir

analyze_escapes, bu elemanları en alt seviyeden en üst seviyeye, Julia'nın yerel tür çıkarım rutinine paralel bir yönde geçirecektir. Soyut bir durum, en alt (-benzeri) elemanlarla başlatılacaktır:

  • çağrı argümanları ArgEscape() olarak başlatılır, Liveness özelliği 0'ı içerir, bu da çağrı argümanı olarak geçirildiğini ve çağırandan hemen görünür olduğunu gösterir
  • diğer durumlar NotAnalyzed() olarak başlatılır, bu, NoEscape'den biraz daha düşük olan özel bir ızgara elemanıdır, ancak aynı zamanda henüz analiz edilmediğinden başka bir anlam ifade etmez (bu nedenle resmi olarak ızgaranın bir parçası değildir)
source

  • 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 cases AliasInfo 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.