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-freeImmutableArray
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))::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
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 nesneisbitstype
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ındaReturnEscape
veAllEscape
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, sadecex
'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) üzerindesetfield!
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]: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
, 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]:2 ◌ 1 ─ 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]: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
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))::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 öğ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şturmaYerel 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_escapes
— Functionanalyze_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
Core.Compiler.EscapeAnalysis.EscapeState
— Typeestate::EscapeState
Argümanları ve SSA değerlerini EscapeInfo
olarak temsil edilen kaçış bilgisine haritalayan genişletilmiş ızgara. SSA IR öğesi x
üzerinde dayatılan kaçış bilgisi estate[x]
ile alınabilir.
Core.Compiler.EscapeAnalysis.EscapeInfo
— Typex::EscapeInfo
Kaçış bilgileri için bir ızgara, aşağıdaki özellikleri tutar:
x.Analyzed::Bool
: ızgaranın resmi bir parçası değildir, yalnızcax
'in analiz edilip edilmediğini gösterirx.ReturnEscape::Bool
:x
'in geri dönüş yoluyla çağırana kaçabileceğini gösterirx.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österirx.AliasInfo === true
x
'in alanlarının/elemanlarının analiz edilemeyeceğini gösterir, örneğinx
'in tipi bilinmiyor veya somut değil ve dolayısıyla alanlarının/elemanlarının kesin olarak bilinmesi mümkün değilx.AliasInfo::IndexableFields
kesin indeks bilgisi ilex
nesnesinin alanlarına taklit edilebilecek tüm olası değerleri kaydederx.AliasInfo::IndexableElements
kesin indeks bilgisi ilex
dizisinin elemanlarına taklit edilebilecek tüm olası değerleri kaydederx.AliasInfo::Unindexable
kesin indeks bilgisi olmadanx
'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 gelirAllEscape()
: 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ği0
'ı 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)
- 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.