# Jameson/Jeff/Shuhei [TOC] ## TODO - EscapeAnalysis.jl - lattice overhaul - compiler-plugin ### EscapeAnalysis.jl - [x] field/alias analysis - [x] array primitives support - [x] implement escape propagation via exceptions: <https://github.com/aviatesk/EscapeAnalysis.jl/pull/72> - [x] pre-inlining analysis: <https://github.com/aviatesk/EscapeAnalysis.jl/pull/87> - [ ] SROA - [ ] [optimizer: EscapeAnalysis.jl-powered SROA](https://github.com/JuliaLang/julia/pull/43888) - [ ] [optimizer: simple array SROA](https://github.com/JuliaLang/julia/pull/43909) - [ ] `ImmutableArray` - [x] implement `mutating_arrayfreeze` optimization with EA.jl - [x] broaden optimization possibilities by ignoring unhandled `ThrownEscape` - [ ] no-capturing `BoundsError`: https://github.com/JuliaLang/julia/pull/43738 - [ ] behavior tests - [x] how to port to Base - [ ] **check latency problem**: merge separately? - [ ] finalizer elision - [ ] IPO information propagation to LLVM ### Lattice overhaul - [x] step 1. separate contexts of native Julia types and extended lattice wrappers - [x] step 2. lattice wrappers to lattice properties - [ ] step 3. implementation clean up, performance tuning - [x] tfuncs - [x] `tmerge` - [ ] `⊑` - [ ] step 4. experiments - [ ] `MustAlias`: [#41199](https://github.com/JuliaLang/julia/pull/41199) - [ ] `Defined`: propagate from `:new`, `Conditional` with `isdefine` ### compiler plugin - try OC-based plugin system - revive `#41632` - missing feature supports - [ ] `_apply_iterate` - [ ] `isva` method - use overlayed method table? ## 2022/09/19 - 2022/09/25 - `:static_parameter`: - ```julia julia> check(::Type{<:Ref{S}}) where S = println(S) check (generic function with 2 methods) julia> check(Ref{Tuple{T}} where T<:Integer) ERROR: UndefVarError: S not defined Stacktrace: [1] check(#unused#::Type{Ref{Tuple{S}} where S<:Integer}) @ Main ./REPL[14]:1 [2] top-level scope @ REPL[15]:1 ``` - ```julia f(::Ref{T}) where T = T Core.Compiler.is_nothrow(Base.infer_effects(f, (Ref,))) ``` - `Test.constrains_param` - `Core.Compiler.return_type`: https://github.com/JuliaLang/julia/pull/46810 ## 2022/08/29 - 2022/09/02 - last week: helping out Keno with Ceder - implement `compilerbarrier` builtin and redefine `inferencebarrier` with it #46432 - SSAIR/Cthulhu printing improvements - implemented customized inlining heuristics for DAECompiler - this week: keep helping out! - finish up new lattice extension system - others: - trying to improve constant propagation heuristics with a very simple backward dataflow analysis (built on top of the effect analysis infrastructure) #46471 ## 2022/07/18 - 2022/07/22 - compiler stdlib - define `Core.Compiler` within `Base` - Makefile: `corecompiler` - invalidations? ## 2022/07/04 - 2022/07/08 - released JET v0.6 - 1.8 compatibility, UI improvements, more configuration layers - finished `:meta` propagation for kwfuncs: https://github.com/JuliaLang/julia/pull/45041 - motivated by [this Discourse thread](https://discourse.julialang.org/t/why-is-the-kwarg-dims-not-being-constant-propagated/83610/5) - worried about the compilation slowdown: seems to happen within Julia-level compilation? (appearing within JET/Cthulhu too) ```julia ~/julia/julia5 master ⇣ ❯ jlb -e 'using Cthulhu; @time Cthulhu.@interp sum(rand(1:100))' 5.256613 seconds (1.96 M allocations: 132.617 MiB, 0.56% gc time, 99.93% compilation time) ~/julia/julia5 master ⇣ ❯ jlb -e 'using Cthulhu; @time Cthulhu.@interp sum(rand(1:100))' 5.100521 seconds (1.96 M allocations: 132.617 MiB, 0.64% gc time, 99.94% compilation time) ~/julia/julia5 master ⇣ ❯ jlb -e 'using Cthulhu; @time Cthulhu.@interp sum(rand(1:100))' 5.169596 seconds (1.96 M allocations: 132.617 MiB, 0.54% gc time, 99.94% compilation time) ``` ```julia ~/julia/julia4 remotes/origin/backports-release-1.8 ❯ jlb -e 'using Cthulhu; @time Cthulhu.@interp sum(rand(1:100))' 3.425331 seconds (2.82 M allocations: 139.480 MiB, 0.65% gc time, 99.78% compilation time) ~/julia/julia4 remotes/origin/backports-release-1.8 ❯ jlb -e 'using Cthulhu; @time Cthulhu.@interp sum(rand(1:100))' 3.348820 seconds (2.82 M allocations: 139.480 MiB, 0.64% gc time, 99.90% compilation time) ~/julia/julia4 remotes/origin/backports-release-1.8 ❯ jlb -e 'using Cthulhu; @time Cthulhu.@interp sum(rand(1:100))' 3.382658 seconds (2.82 M allocations: 139.480 MiB, 0.60% gc time, 99.90% compilation time) ``` - plan for this week: - debug the slowdown - start working on compiler stdlib ## 2022/06/27 - 2022/07/01 - finalizing 1.8 support for JET - may need some formal "type error" definitions for JET? - currently: "no rules!" - `MethodError` happening at `:call`: always caught - `throw`n errors: only propagates when return type is `Bottom` - 1.8: concrete evaluation - some "error"s are detected by concrete evaluation - e.g. `MethodError`, arbitrary `throw`s... - needs to define what is "serious", and what is not? ## 2022/06/13 - 2022/06/17 - `MustAlias` PR is ready for merge - one spurious regression in `["collection", "deletion", ("IdDict", "Any", "filter")]` - any possible performance consideration for generating `getfield(x::Some{Union{Nothing,T}}, :value)::T`? ```diff diff --git a/_master b/_pr index bf8d08a220..7c79f0bb24 100644 --- a/_master +++ b/_pr @@ -1,5 +1,5 @@ filter(f, d::AbstractDict) in Base at abstractdict.jl:471 -│ ─ %-1 = invoke filter(::var"#14#15"{IdDict},::IdDict{Any, Any})::IdDict{Any, Any} +│ ─ %-1 = invoke filter(::var"#20#21"{IdDict},::IdDict{Any, Any})::IdDict{Any, Any} 1 ── %1 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Any}, svec(Any, Int64), 0, :(:ccall), Vector{Any}, 32, 32))::Vector{Any} │ %2 = %new(IdDict{Any, Any}, %1, 0, 0)::IdDict{Any, Any} │ %3 = Base.getfield(d, :ht)::Vector{Any} @@ -20,14 +20,14 @@ filter(f, d::AbstractDict) in Base at abstractdict.jl:471 │ %18 = φ (#3 => %15)::Int64 │ %19 = φ (#3 => %11)::Any │ %20 = φ (#3 => %14)::Any -│ %21 = φ (#3 => %11)::Any +│ %21 = φ (#3 => %11)::Int64 └─── goto #5 5 ── %23 = Base.not_int(%17)::Bool └─── goto #21 if not %23 6 ┄─ %25 = φ (#5 => %18, #20 => %59)::Int64 │ %26 = φ (#5 => %19, #20 => %60)::Any │ %27 = φ (#5 => %20, #20 => %61)::Any -│ %28 = φ (#5 => %21, #20 => %62)::Any +│ %28 = φ (#5 => %21, #20 => %62)::Int64 │ %29 = (%26 isa Main.Int)::Bool └─── goto #8 if not %29 7 ── invoke Base.setindex!(%2::IdDict{Any, Any}, %27::Any, %28::Any)::Any @@ -61,7 +61,7 @@ filter(f, d::AbstractDict) in Base at abstractdict.jl:471 19 ┄ %59 = φ (#18 => %57)::Int64 │ %60 = φ (#18 => %53)::Any │ %61 = φ (#18 => %56)::Any -│ %62 = φ (#18 => %53)::Any +│ %62 = φ (#18 => %53)::Int64 │ %63 = φ (#17 => true, #18 => false)::Bool │ %64 = Base.not_int(%63)::Bool └─── goto #21 if not %64 @@ -72,6 +72,6 @@ Toggles: [o]ptimize, [w]arn, [h]ide type-stable statements, [d]ebuginfo, [r]emar Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code Actions: [E]dit source code, [R]evise and redisplay Advanced: dump [P]arams cache. - • %31 = invoke setindex!(::IdDict{Any, Any},::Any,::Any)::Any + • %31 = invoke setindex!(::IdDict{Any, Any},::Any,::Int64)::Any %37 = invoke throw_inexacterror(::Symbol,::Type{UInt64},::Int64)::Union{} ↩ ``` - `TypedSlot` for `SSAValue`? - re-super quadratic time: - the biggest remaining factor is at LLVM: - `MachineFunctionPass` then `GVN` and `SLPvectorizer` - best way to speed this up further? ## 2022/05/23 - 2022/05/27 - quadratic inference - performance - [the original target](https://github.com/JuliaComputing/CompilerTeamPriorities/issues/20#issuecomment-1131395758) ``` before: 563.981536 seconds (24.63 M allocations: 23.534 GiB, 82.65% gc time, 99.94% compilation time) after: 72.051300 seconds (24.76 M allocations: 1.650 GiB, 0.84% gc time, 99.52% compilation time) ``` - new [`quadratic`](https://github.com/JuliaCI/BaseBenchmarks.jl/blob/06da8005d4872ed627b5fdfe7a9293448e291d08/src/inference/InferenceBenchmarks.jl#L168-L181) benchmark case: ``` BenchmarkTools.Trial: 14 samples with 1 evaluation. Range (min … max): 847.593 ms … 1.188 s ┊ GC (min … max): 11.48% … 34.85% Time (median): 858.654 ms ┊ GC (median): 12.11% Time (mean ± σ): 895.919 ms ± 88.650 ms ┊ GC (mean ± σ): 14.01% ± 6.16% ██ ██▁▆▁▁▁▁▁▆▆▁▆▁▆▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆ ▁ 848 ms Histogram: frequency by time 1.19 s < BenchmarkTools.Trial: 42 samples with 1 evaluation. Range (min … max): 241.295 ms … 319.025 ms ┊ GC (min … max): 0.00% … 0.00% Time (median): 241.929 ms ┊ GC (median): 0.00% Time (mean ± σ): 247.420 ms ± 17.695 ms ┊ GC (mean ± σ): 0.00% ± 0.00% █ █▆▅▁▁▁▁▅▁▁▁▁▁▁▁▁▁▁▁▁▅▁▁▁▁▁▁▁▁▁▁▁▁▁▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▅ ▁ 241 ms Histogram: log(frequency) by time 319 ms < ``` - general case: some minor regressions (in range of 2~5%) - accuracy: passes all Base test suite, no `pkgeval` issues - nanosoldier problems - seems unable to run on a PR: [e.g.](https://github.com/JuliaLang/julia/pull/45276#issuecomment-1135782901) - want to merge `master` to `nanosolidier` branch - lucky compiler performance improvement! - [SSAIR: improve inlining performance with in-place IR-inflation #45404](https://github.com/JuliaLang/julia/pull/45404/) - time: 3% faster - memory: cut 3~7% - JET - want to have some time to work on 1.8 compatibility - ship with Base? - some discussion on slack: https://juliacomputing.slack.com/archives/CB7T6RHHD/p1653341883386499?thread_ts=1653340013.110119&cid=CB7T6RHHD ## 2022/05/09 - 2022/05/13 - [x] bisect `union!` regression - [ ] finish [the refactoring](https://github.com/JuliaLang/julia/pull/45098) - [x] per-BB state analysis - [ ] discuss more compiler ideas? ## 2022/04/25 - 2022/04/28 Taint analysis: - taint analysis provides some useful insights about abstract interpretation - for better constant propagation heuristics - "is this constant argument involved with return type calculation?" - "does this constant argument influence control flow?" - for better inlining decision? - "is this argument called on `getfield`?" - "graph pruning" (Jameson) - design: - along with absint => better IPO propagation - _flow-insensitive_: start with the simplest - _forward_: how can taints be propagated effectively in the forward analysis? - comparison with alias analysis ## 2022/04/18 - 2022/04/22 Shuhei work list: - [x] correct backedge optimization - [x] update LoweredCodeUtils/Revise - [x] JET benchmark suite overhaul - [ ] make JET compatible with 1.8 - [ ] fix bug of `report_opt` (from [discourse](https://discourse.julialang.org/t/why-does-jet-give-these-runtime-dispatch-detected-lines-for-some-ldlfactorizations-calls-in-tulip/79583/4)) * [ ] analysis routine for concrete evaluation * [ ] update with internal changes around typed global variable - [ ] refactor JET and merge [`avi/optlifetime`](https://github.com/JuliaLang/julia/pull/43994) - [ ] analysis infrastructure for `CodeInfo` - [ ] experiment on static dispatch handling for compiler plugin - [ ] world age approach - [ ] new field for plugin cache --- - investigated inferrability of `cat`: <https://github.com/JuliaLang/julia/pull/45028> - return type profitability for "method instance heuristics"? - currently it only accounts for inlining profitability - macro annotation for keyword method definitions? ```julia # `@inline` annotation isn't effective to force @inline cat_t(::Type{T}, X...; dims) where {T} = _cat_t(dims, T, X...) ``` ## 2022/04/11 - 2022/04/15 Shuhei work list: - [ ] refactor JET and merge [`avi/optlifetime`](https://github.com/JuliaLang/julia/pull/43994) - [ ] make JET compatible with 1.8 - [ ] finish [`Core.Compiler.infer_effects`](https://github.com/JuliaLang/julia/pull/44822) - [x] finish [`@pure` replacements](https://github.com/JuliaLang/julia/pull/44776) - [x] fix [1.8 regression](https://github.com/JuliaLang/julia/issues/44965) - [ ] make a progress on compiler plugin - [x] segfault - [ ] experiment on static dispatch handling --- - `Core.Compiler.infer_effects` - Jeff: sounds unreliable/dangerous (yes, it is), don't like it! - xref: <https://github.com/JuliaLang/julia/issues/35800> - Jameson: let's fold `infer_effects` at optimization time (rather than inference time) - not all information is necessarily stable at a point of abstract interpretation - propagate information only for optimization, not inter-procedurally - `:invoke` handling for compiler plugin - interpreter: - `do_invoke` (interpreter.c) - `jl_invoke`/`_jl_invoke` (gf.c) - access to cache: `codeinst = jl_atomic_load_relaxed(&mfunc->cache)` - invoke: `jl_atomic_load_relaxed(&codeinst->invoke)(...)` - codegen: - `emit_invoke` (codegen.cpp) - TODO - set `(mi::MethodInstance).cache` manually - `jl_mi_cache_insert` (used by `setindex!(::NativeInterpreter, ...)` and also other several compilation points) - with some sort of overlayed `MethodInstance`, or setup specific fields? - use dedicated world age? ## 2022/04/05 - some ideas to cut off latency - background: the replacements of `@pure` faces the general latency problem (good correctness, less performance) - for `@nospecialize` methods: don't use type-specialization mechanism, but use constant-propagation only instead - xref: <https://github.com/JuliaLang/julia/pull/41931> - compiler plugin - wip at <https://github.com/aviatesk/julia/pull/1> - problems: - [ ] segfaults: <https://github.com/JuliaLang/julia/pull/44197#discussion_r837432494> - [x] dynamic dispatch (with a non-composable approach) - [ ] `:invoke` - check how the runtime system looks up a cache for `:invoke` call - implement an interface to inject custom lookup - JET - JET's optimization analysis implementation needs refactoring before merging [`AbstractInterpreter`: refactor the lifetimes of `OptimizationState` and `IRCode`](https://github.com/JuliaLang/julia/pull/43994) - that PR would be very useful for compiler plugin - some updates are required for 1.8 release - changes with concrete evaluation: [spurious dispatch report from `@report_opt log(2.1)`](https://github.com/aviatesk/JET.jl/issues/334) - changes with type annotations on global variables - (optimization analysis should be more careful about inlined code: [optimization analysis should not report duplicated error from inlined callees](https://github.com/aviatesk/JET.jl/issues/335)) - random Qs. - [x] https://github.com/JuliaLang/julia/pull/44761 waits review from Jeff :) - [ ] https://github.com/JuliaLang/julia/pull/44776 is ready for merge? - [ ] https://github.com/JuliaLang/julia/pull/44822 how much `Core.Compiler.infer_effects` should take care of uncertainty? ## 2022/03/29 - was in a good condition: 15 PRs in a week! - OC-based plugin system - wip at <https://github.com/aviatesk/julia/pull/1> - problems: - [ ] segfaults: <https://github.com/JuliaLang/julia/pull/44197#discussion_r837432494> - [x] dynamic dispatch (not very composable though) - [ ] `:invoke`: how the runtime system looks up a cache for `:invoke` call? - miscellaneous questions: - return-type reflection on OC - <https://github.com/JuliaLang/julia/pull/44743> - `code_typed`/`Base.return_types` don't work well on OC ```julia julia> let foo::Int = 42 Base.Experimental.@force_compile oc = Base.Experimental.@opaque a::Int->sin(a) + cos(foo) last(only(code_typed(oc, (Int,)))), only(Base.return_types(oc, (Int,))) end (Any, Any) ``` - `oc::OpaqueClosure{Tuple{Int64}, Float64}` - [`tuple_tfunc` on `Union` containing `Type{...}`](https://github.com/JuliaLang/julia/pull/44725) - prob: `tuple_tfunc(Union{Type{Int32},Type{Int64}}) => Tuple{Union{Type{Int32}, Type{Int64}}}` - but no instance can have a runtime type `Tuple{Union{Type{...},Type{...}}` - fix: `tuple_tfunc(Union{Type{Int32},Type{Int64}}) === Tuple{Type} # not Tuple{Union{Type{Int32}, Type{Int64}}}` - new prob: error from a case that was wrongly erased before ```julia function assertx(xs::Vector{T}) where T t = (xs[1], xs[2]) t::Tuple{T,T} end @code_typed optimize=false assertx(Type{<:Number}[Int,Int]) @code_typed optimize=true assertx(Type{<:Number}[Int,Int]) ``` ```diff diff --git a/_master.jl b/_pr.jl index 03768efc773..54ccceaa2cc 100644 --- a/_master.jl +++ b/_pr.jl @@ -2,8 +2,8 @@ julia> @code_typed optimize=false assertx(Type{<:Number}[Int,Int]) CodeInfo( 1 ─ %1 = Base.getindex(xs, 1)::Type{<:Number} │ %2 = Base.getindex(xs, 2)::Type{<:Number} -│ (t = Core.tuple(%1, %2))::Tuple{Type{<:Number}, Type{<:Number}} -│ %4 = t::Tuple{Type{<:Number}, Type{<:Number}} +│ (t = Core.tuple(%1, %2))::Tuple{Type, Type} +│ %4 = t::Tuple{Type, Type} │ %5 = Core.apply_type(Main.Tuple, $(Expr(:static_parameter, 1)), $(Expr(:static_parameter, 1)))::Core.Const(Tuple{Type{<:Number}, Type{<:Number}}) │ %6 = Core.typeassert(%4, %5)::Tuple{Type{<:Number}, Type{<:Number}} └── return %6 @@ -13,6 +13,8 @@ julia> @code_typed optimize=true assertx(Type{<:Number}[Int,Int]) CodeInfo( 1 ─ %1 = Base.arrayref(true, xs, 1)::Type{<:Number} │ %2 = Base.arrayref(true, xs, 2)::Type{<:Number} -│ %3 = Core.tuple(%1, %2)::Tuple{Type{<:Number}, Type{<:Number}} -└── return %3 +│ %3 = Core.tuple(%1, %2)::Tuple{Type, Type} +│ Core.typeassert(%3, Tuple{Type{<:Number}, Type{<:Number}})::Tuple{Type{<:Number}, Type{<:Number}} +│ %5 = π (%3, Tuple{Type{<:Number}, Type{<:Number}}) +└── return %5 ) => Tuple{Type{<:Number}, Type{<:Number}} ``` * a same problem can happen for `convert` within `setindex!(::Vector{Tuple{Type{...},...}, ...)`) (e.g. SquadGames.jl) * should we just prohibit this container type? then we don't have a way to express `Container{Type{<:T}}`? ## 2022/03/22 - stand-up projects? - #44512 - compiler-plugin: - wrap generated code within OC - i.e. work on Diffractor - report something at next-week! - customized analysis - Jeff wants to delay it: what they need is not clear - will let me know if it turns out be really important ## 2022/03/15 ```julia= julia> f(x::Int, y::Int) = 1 f (generic function with 1 method) julia> f(x::Any, y::Int) = 2 f (generic function with 2 methods) julia> f(x::Int, y::Any) = 3 f (generic function with 3 methods) julia> code_typed((Any,Any)) do x, y f(x, y) end 1-element Vector{Any}: CodeInfo( 1 ─ %1 = (isa)(x, Int64)::Bool │ %2 = (isa)(y, Int64)::Bool │ %3 = (Core.Intrinsics.and_int)(%1, %2)::Bool └── goto #3 if not %3 2 ─ goto #8 3 ─ %6 = (isa)(x, Int64)::Bool └── goto #5 if not %6 4 ─ goto #8 5 ─ %9 = (isa)(y, Int64)::Bool └── goto #7 if not %9 6 ─ goto #8 7 ─ %12 = Main.f(x, y)::Int64 └── goto #8 8 ┄ %14 = φ (#2 => 1, #4 => 3, #6 => 2, #7 => %12)::Int64 └── return %14 ) => Int64 julia> fr(x::Int, y::Int) = 1 fr (generic function with 3 methods) julia> fr(x::Real, y::Int) = 2 fr (generic function with 3 methods) julia> fr(x::Int, y::Real) = 3 fr (generic function with 3 methods) julia> code_typed((Any,Any)) do x, y fr(x, y) end 1-element Vector{Any}: CodeInfo( 1 ─ %1 = (isa)(x, Int64)::Bool │ %2 = (isa)(y, Int64)::Bool │ %3 = (Core.Intrinsics.and_int)(%1, %2)::Bool └── goto #3 if not %3 2 ─ goto #8 3 ─ %6 = (isa)(x, Int64)::Bool │ %7 = (isa)(y, Real)::Bool │ %8 = (Core.Intrinsics.and_int)(%6, %7)::Bool └── goto #5 if not %8 4 ─ goto #8 5 ─ %11 = (isa)(x, Real)::Bool │ %12 = (isa)(y, Int64)::Bool │ %13 = (Core.Intrinsics.and_int)(%11, %12)::Bool └── goto #7 if not %13 6 ─ goto #8 7 ─ %16 = Main.fr(x, y)::Int64 └── goto #8 8 ┄ %18 = φ (#2 => 1, #4 => 3, #6 => 2, #7 => %16)::Int64 └── return %18 ) => Int64 ``` ```julia= ncases = length(cases) for i = 1:ncases sigᵢ = cases[i].sig isa(sigᵢ, DataType) || return nothing for j = i+1:ncases sigⱼ = cases[j].sig # since we already bail out from ambiguous case, we can use `morespecific` as # a strict total order of specificity (in a case when they don't have a type intersection) # if !(!hasintersect(sigᵢ, sigⱼ) || morespecific(sigᵢ, sigⱼ)) # filter!(case::InliningCase->isdispatchtuple(case.sig), cases) # length(cases) > 0 || return nothing # @goto add_unionsplit_todo! # end end end @label add_unionsplit_todo! push!(todo, idx=>UnionSplit(fully_covered, atype, cases)) ``` ## 2022/03/08 - technical questions - reviews on [`AbstractInterpreter`: implement `findsup` for `OverlayMethodTable`](https://github.com/JuliaLang/julia/pull/44448) > It only matters if `result[end].fully_covers` * `jl_matching_methods` returns matching methods in order of speciality (and so the last match is always the most general case) * if the last match fully_covers, it means this call is guaranteed to be dispatched to the last match when it's not dispatched with the other matches * ambiguity: not ordered - <https://github.com/JuliaLang/julia/pull/44512> - <https://github.com/JuliaLang/julia/pull/44515> - Shuhei' job opportunity at JC - timeline? - place to start the job: within a week! - => a week!? - tasks, goals - salary?: probably okay rely on their initial request! - next step: a meeting with Jeff (and Jameson) - what we want to do at JC - what we're interested ## 2022/02/22 - compiler-plugin minimal requirements for completing Diffractor.jl 1. "overdubbing" mechanism implemented within the native compilation pipeline 2. integration with `AbstractInterpreter` - the differences from Cassette.jl 1. code transformation is triggered on code specialization, not on dispatch => inference can go through even type-instable programs 2. giving opportunities to customize inference/optimizations - references - Cassette.jl is well designed in general - many missing `@nospecialize`s - useful implementations/utilities - `method_for_inference_limit_heuristics` - `Base.Meta.partially_inline!` - is its "tagging" system important? > code transformation with abstract types ```julia= julia> using Cassette julia> Cassette.@context SinCtx Cassette.Context{nametype(SinCtx)} julia> sin_plus_cos(x) = sin(x) + cos(x) sin_plus_cos (generic function with 1 method) julia> global X::Any = rand() 0.6740577198124015 julia> code_typed() do @inline Cassette.@overdub SinCtx() sin_plus_cos(X::Int) end 1-element Vector{Any}: CodeInfo( 1 ─ %1 = Main.X::Any │ %2 = Cassette.overdub($(QuoteNode(Cassette.Context{nametype(SinCtx), Nothing, Nothing, Cassette.var"##PassType#302", Nothing, Nothing}(nametype(SinCtx)(), nothing, nothing, Cassette.var"##PassType#302"(), nothing, nothing))), Core.typeassert, %1, Main.Int)::Any │ %3 = Cassette.overdub($(QuoteNode(Cassette.Context{nametype(SinCtx), Nothing, Nothing, Cassette.var"##PassType#302", Nothing, Nothing}(nametype(SinCtx)(), nothing, nothing, Cassette.var"##PassType#302"(), nothing, nothing))), Main.sin_plus_cos, %2)::Any └── return %3 ) => Any ``` ## 2022/02/15 - EA: couldn't make it for 1.8 - problem: it's harder than expected to implement SROA _properly_ - root problem: EA encodes (mostly) global information vs. optimization passes usually require flow sensitive information ```julia= # examples where flow-insensitive AliasInfo is problematic let if cond x = Ref("x") else x = Ref("y") end return x[] end let x = Ref("x") y = Ref("y") if cond z = x else z = y end z[] # field info: [LocalDef(SSAValue(1)), LocalDef(SSAValue(2))] end let x = Ref("x") y = Ref("y") ifelse(cond, x, y)[] # field info: [LocalDef(SSAValue(1)), LocalDef(SSAValue(2))] end # `avi/EASROA` can be complicated by aliasing via π-node as well ``` ## 2022/02/01 - EA: IPO escape information - [x] ["run EA before inlining for generating IPO cache"](https://github.com/aviatesk/EscapeAnalysis.jl/pull/87) - consumers - local EA powered by interprocedural escape information - `mutating_arrayfreeze` - finalizer elision - load forwarding - type inference? - `Const` / `PartialStruct` / `MustAlias` for mutables - consideration: how to add backedges properly? - might contribute to latency problem: not likely (since types are mostly concrete if we have successful escape information) - when to run: pre-inlining - ok: optimized frame - COMBAK: unoptimized frames (those within a cycle) - needs to be able to run EA on `CodeInfo` - => maybe not needed anyway: even inference results can be discarded ## 2022/01/18 - EscapeAnalysis.jl: pointer/`:foreigncall` handling ([`#76`](https://github.com/aviatesk/EscapeAnalysis.jl/pull/76)) - pointer - don't try to analyze pointer operations - escapes on pointer construction (`jl_value_ptr`) - escapes on `pointerset`: valid Julia object can't be passed to this function - aliasing information: `isbitstype` objects can be aliased via pointer operations - EA doesn't need to account for escapes via pointer operations (just because it's beyond our GC management) - for more load-forwarding, we may want to track this aliasing (but probably in the future) - `:foreigncall` - call arguments: impose `AllEscape` - preserved arguments: setup `PreserveEscape`? ```julia julia> code_typed((Any,Nothing,Int,UInt)) do t, mt, lim, world ambig = false min = Ref{UInt}(typemin(UInt)) max = Ref{UInt}(typemax(UInt)) has_ambig = Ref{Int32}(0) mt = ccall(:jl_matching_methods, Any, (Any, Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}, Ref{Int32}), t, mt, lim, ambig, world, min, max, has_ambig) return mt, has_ambig[] end 1-element Vector{Any}: CodeInfo( 1 ─ %1 = %new(Base.RefValue{UInt64}, 0x0000000000000000)::Base.RefValue{UInt64} │ %2 = %new(Base.RefValue{UInt64}, 0xffffffffffffffff)::Base.RefValue{UInt64} │ %3 = %new(Base.RefValue{Int32}, 0)::Base.RefValue{Int32} │ %4 = Core.trunc_int(Int32, lim)::Int32 │ %5 = Core.sext_int(Int64, %4)::Int64 │ %6 = Core.eq_int(lim, %5)::Bool └── goto #3 if not %6 2 ─ goto #4 3 ─ invoke Core.throw_inexacterror(:trunc::Symbol, Int32::Type{Int32}, lim::Int64)::Union{} └── unreachable 4 ─ goto #5 5 ─ goto #6 6 ─ goto #7 7 ─ goto #8 8 ─ %15 = $(Expr(:foreigncall, :(:jl_value_ptr), Ptr{Nothing}, svec(Any), 0, :(:ccall), :(%1)))::Ptr{Nothing} │ %16 = Base.bitcast(Ptr{UInt64}, %15)::Ptr{UInt64} │ %17 = $(Expr(:foreigncall, :(:jl_value_ptr), Ptr{Nothing}, svec(Any), 0, :(:ccall), :(%2)))::Ptr{Nothing} │ %18 = Base.bitcast(Ptr{UInt64}, %17)::Ptr{UInt64} │ %19 = $(Expr(:foreigncall, :(:jl_value_ptr), Ptr{Nothing}, svec(Any), 0, :(:ccall), :(%3)))::Ptr{Nothing} │ %20 = Base.bitcast(Ptr{Int32}, %19)::Ptr{Int32} │ %21 = $(Expr(:foreigncall, :(:jl_matching_methods), Any, svec(Any, Any, Int32, Int32, UInt64, Ptr{UInt64}, Ptr{UInt64}, Ref{Int32}), 0, :(:ccall), Core.Argument(2), Core.Argument(3), :(%4), 0, Core.Argument(5), :(%16), :(%18), :(%20), :(%3), :(%2), :(%1), Core.Argument(5), 0, :(%4)))::Any │ %22 = Base.getfield(%3, :x)::Int32 │ %23 = Core.tuple(%21, %22)::Tuple{Any, Int32} └── return %23 ) => Tuple{Any, Int32} ``` - only `Any` arguments can escape, otherwise it doesn't escape (often) - lattice overhaul: one annoying allocation regression - needs to hear general tips for debug - no obvious difference in `code_typed` level - may require some madness to descend with Cthulhu.jl ```julia using Test, SparseArrays, LinearAlgebra, Random normpath("stdlib/SparseArrays-205b7703b91477e6c43d8c125a0f2f486ab30cfd/test/forbidproperties.jl") begin N, M, p = 10, 12, 0.5 elT = Float64 s = Float32(2.0) V = sprand(elT, N, p) Vᵀ = transpose(sprand(elT, 1, N, p)) A = sprand(elT, N, M, p) Aᵀ = transpose(sprand(elT, M, N, p)) fV, fA, fVᵀ, fAᵀ = Array(V), Array(A), Array(Vᵀ), Array(Aᵀ) # test combinations involving one to three scalars and one to five sparse vectors/matrices spargseq, dargseq = Iterators.cycle((A, V, Aᵀ, Vᵀ)), Iterators.cycle((fA, fV, fAᵀ, fVᵀ)) (sparseargs, denseargs) = ((V, A, V, A, s, V, A, V), (fV, fA, fV, fA, s, fV, fA, fV)) # test broadcast entry point @test broadcast(*, sparseargs...) == sparse(broadcast(*, denseargs...)) @test isa(@inferred(broadcast(*, sparseargs...)), SparseMatrixCSC{elT}) # test broadcast! entry point fX = broadcast(*, sparseargs...); X = sparse(fX) @test broadcast!(*, X, sparseargs...) == sparse(broadcast!(*, fX, denseargs...)) @test isa(@inferred(broadcast!(*, X, sparseargs...)), SparseMatrixCSC{elT}) X = sparse(fX) # reset / warmup for @allocated test @test (@allocated broadcast!(*, X, sparseargs...)) <= 900 end ``` - related to inference limit? so Cthulhu remarks might be useful? - rr with setting breaking points on `jl_call` - compiler plugin: method table replacement? - motivation: ```julia # user code h(x) = sin(x) g(x::Float64) = h(x) f(x) = g(x) SymPlugin(f, 42.0) # overdubbed code # Cassette will assume there is method for `g(::Sym{Float64})` f(x::Sym{Float64}) = SymPlugin(g, x::Sym{Float64}) # want this to be resolved as `g(::Float64)` g(x::Sym{Float64}) = SymPlugin(h, x::Sym{Float64}) h(x::Sym{Float64}) = SymPlugin(sin, x::Sym{Float64}) # primitive handling ``` - `@method_overlay` trick can be used? ## 2022/01/11 - EscapeAnalysis.jl - exception handling - <https://github.com/aviatesk/EscapeAnalysis.jl/pull/72> - access to exceptions: - `:the_exception`: local - `Base.current_exceptions`: global - `rethrow` & nested `try/catch`: global - exceptions will be appended to exception stack -> cleared on exit - just propagate `AllEscape` for now - Jameson: we may not want to change it in the near future - `ImmutableArray` optimization enhancement - ignore unhandled `ThrownEscape` ```julia function mka(n::Int) xs = collect(1:n) rand(Bool) && throw(xs) return Core.ImmutableArray(xs) end ``` - ignore irrelevant `ReturnEscape`? ```julia xs = collect(1:n) rand(Bool) && return xs return Core.ImmutableArray(xs) ``` - no-capturing `BoundsError` - https://github.com/JuliaLang/julia/pull/43738 - broaden optimization possibilities for cases like: ```julia xs = [...] try r = xs[i] catch err # ... # AllEscape r = nothing end return r, ImmutableArray(xs) ``` - a best way to port to Base? - ~~stdlib~~: once we have compiler plugin infrastructure? - develop in `base/compiler` => most straight forward - lattice overhaul - good progress: step 3. almost done - TODO: fix allocation regression ``` ~/julia/julia avi/typelattice 1m 33s ❯ ./usr/bin/julia --project=~/julia/plot -e "@time using Plots; @time plot(rand(10,3));" 4.259995 seconds (8.53 M allocations: 572.333 MiB, 3.67% gc time, 21.17% compilation time) 5.537299 seconds (15.75 M allocations: 977.495 MiB, 5.39% gc time, 99.85% compilation time) ~/julia/julia3 master 23s ❯ ./usr/bin/julia --project=~/julia/plot -e "@time using Plots; @time plot(rand(10,3));" 4.138357 seconds (7.58 M allocations: 521.207 MiB, 3.07% gc time, 20.49% compilation time) 5.110887 seconds (14.14 M allocations: 814.826 MiB, 5.36% gc time, 99.83% compilation time) ``` ## 2021/12/21 - SROA update - succeeded in SROA of mutable `PhiNode`s ```julia let r = Ref(s::String) for x in xs r = Ref(x::String) end r[] # φ end ``` - (but) it turns out LLVM already does good work for `isbitstype` - probably needs better abstraction (implementation complexity) - EA update - focus on completing `ImmutableArray` work - allows other optimizations to opt-in gradually (e.g. SROA) - add tests for EA, and make it easier to port tests as well! - lattice-overhaul update - one TODO per day - performance concern - how to allocate `LatticeElement` on stack? - `obj::LatticeElement` - `::Union{Nothing,LatticeElement}` needs to be heap-allocated - => will automatically happens (once we eliminate unncessary conversions)! - extra information - Jameson: "should not hit much performance" - => linked-list data structure? - make `conditional_info` to `special` also ## 2021/11/09 - benchmark suite for compiler - what: - it should be a measure that is more generalized than TTFP - it's obviously better to be able to invoke it in CI - where: BaseBenchmarks.jl? - how: - (repeated compilation) - - `@benchmarkable @code_typed interp=BenchmarkInterpreter(cache) f(...) setup=(clear!(cache))` - define an `AbstractInterpreter` - make it maintain its own global cache - something like: [`CCProfiler.jl`](https://gist.github.com/aviatesk/54ffa6e99d77e7bd8824e3616961de7f) - reliable set of target programs? - is it possible to use Plots.jl's codebase in BaseBenchmarks.jl? - discussion: - can only benchmark Julia-level compilation, not the whole compilation pipeline including LLVM - escape analysis - SROA got enhanced (unintialized fields, nested loads) - change the primary target of EA to partial SROA / stack allocation? ## 2021/10/08 - [x] lattice overhaul progress - [ ] questions about other memory-optimization ideas ### Lattice overhaul - Refactoring plan 1. don't use native Julia types with extended lattice 2. get rid of existing extended lattice wrappers 3. experiment new lattice design with adding new lattice properties - Progress - working at: [`aviatesk/julia/avi/typelattice`](https://github.com/aviatesk/julia/tree/avi/typelattice): - [x] [`cea4c94`](https://github.com/aviatesk/julia/commit/cea4c949c48feee80117bfbfb0b8b98a49ac9866): don't use native Julia types with extended lattice (mostly) - set up temporal `AbstractLattice`, which doesn't include native Julia types - now "most" compiler code works with `AbstractLattice` and `Vector{AbstractLattice}` - [ ] tfuncs - [ ] `abstract_iteration` - [ ] `precise_container_type` - [ ] `tmeet` - status: - [x] bootstrap - [x] sysimg - [x] test (mostly) - [ ] some inference/optimization issue ? - \# of precompiled signantures: `1210/1242` -> `1250/1280` - (excessive inlining ...?) - [ ] get rid of existing extended lattice wrappers - [x] [`543eef6`](https://github.com/aviatesk/julia/commit/543eef6dc1cd58eb17558edb65b45eab1f6b40b5): `PartialStruct` - [ ] `LimitedAccuracy`: might be easy ? - [ ] `Conditional` - [ ] `Const` - [ ] `PartialTypeVar`: would be easy as `PartialStruct` - [ ] ... - [ ] experiment new lattice design with adding new lattice properties - [ ] `MustAlias` / `InterMustAlias`: [`#41199`](https://github.com/JuliaLang/julia/pull/41199) - [ ] `NInitialized`: records # of initialized fields (when not `PartialStruct`-folded) in order to help `stmt_effect_free` - [ ] `DefinedFields`: back-propagate `isdefined` information in order to help `stmt_effect_free` - how to collaborate ? - Shuhei will make PR, keep working on it - Jameson will have reviews on it - (new compiler developers may work on impls ...?) - what is really needed ... ? - complaints about the current lattice design 1. a wrapper can wrap another wrapper 2. most lattice operations are dynamic dispatch 3. `widenconst` / `widenconditional` / `ignorelimited` messes - `widenconst` mess: native Julia types and extended lattice types can appear in the same context - `widenconditional` / `ignorelimited` messes: we need them since they can return more precise result than `widenconst` 4. complex `⊑` and `tmerge` ? - observations: - most complexities are related to "correctness" - complexities can come from high-quality system - how to check a success of this overhaul: with this change, we won't see past issues ? - <https://github.com/JuliaLang/julia/pull/42034> - <https://github.com/JuliaLang/julia/pull/41199> can be reviewed easily ? - solutions 1. a wrapper can wrap another wrapper: **constructor assertions** > e.g. [inference: fix #42090, make sure not to wrap `Conditional` in `PartialStruct` #42091](https://github.com/JuliaLang/julia/pull/42091) ```julia function PartialStruct(@nospecialize(typ), fields::Vector{Any}) for x in fields @assert !isa(x, Conditional) && ... # NOTE: it would be easier if we can write: # @assert x.conditional !== nothing end return new(typ, fields) end ``` 2. ~~most lattice operations are dynamic dispatch~~: **it turns out our dynamic dispatch has a decent performance !** 3. `widenconst` / `widenconditional` / `ignorelimited` messes: **new lattice design ?** - `widenconst` mess: setup own wrapper for native Julia types ```julia struct NativeType typ::Type end ``` - COMBAK: how do we want to wrap `Vararg` ? - `widenconditional` / `ignorelimited` messes: ```julia struct TypeLattice typ::Type const::Union{Nothing,Const} ... conditional::Union{Nothing,Conditional} causes::Union{Nothing,IdSet{InferenceState}} ... end ``` - then we don't need `widenconditional` / `ignorelimited` ? - `widenconditional`: an utility to convert `TypeLattice` w/ `conditional` to `TypeLattice` w/o `conditional` - these utilities can be implemented by `tmerge(cond, ::PartialBottom)` - if we allow `x::TypeLattice` to have `x.const::Const` and `x.conditional::Conditional` at the same time ? 4. complex `⊑` and `tmerge` ?: **orthogonal lattice properties ?** - observations: + implementations of `⊔` / `⊓` / `⊑` can be simpler when the deal with "orthogonal" lattice properties ```julia function ⊔(x::TypeLattice, y::TypeLattice) ... if !isnothing(typea.causes) && !isnothing(typeb.causes) if typea.causes ⊆ typeb.causes causes = typeb.causes elseif typeb.causes ⊆ typea.causes causes = typea.causes else causes = union!(copy(typea.causes), typeb.causes) end elseif !isnothing(typea.causes) causes = typea.causes elseif !isnothing(typeb.causes) causes = typeb.causes else causes = nothing end # goes on ... return TypeLattice(..., causes, ...) end ``` + but some properties can't be handled in orthogonal way ? + => many fields can be unioned separately (great !) + except. `PartialStruct` invariant + =>> the situation might be much better ! - lattice extension system ? - parameterize lattice operations with `AbstractLattice` ? - => outside lattice will invalidate almost all `Core.Compiler` methods ... - => parameterize lattice operations with `AbstractInterpreter` ? ```julia module Compiler # default lattice implementations struct TypeLattice <: AbstractLattice type const::Union{Nothing,Const} fields::Union{Nothing,Vector{Any}} ... end ⊑(x::AbstractLattice, y::AbstractLattice) = ... # default abstract interpretation implementation ... AbstractLattice(::NativeInterpreter) = TypeLattice function abstract_call_gf_by_type(interp::AbstractInterpreter...) ... return CallMeta{AbstractLattice(interp)}(...) end end # Compiler ``` - we still need to define minimum set `AbstractLatice` operations that form lattice interface ... - where did the inspiration come from ? - Crystal: code lattice: vs. type lattice ## 2021/09/22 - <https://github.com/JuliaLang/julia/pull/42357> - `@nospecialize` use declared type instead of observed type - it may be confused with e.g. `Union` declared types - `@nospecialize(a::Union{Integer,Nothing)` - a complex declared type maybe fallback to runtime dispatch - see: <https://github.com/JuliaLang/julia/blob/55808a56e0fcd147b10e35d64721a1849c841196/src/gf.c#L628> - type lattice overhaul - runtime dispatch cache is very fast - sort dispatch table based on occurance - doing something like profile-guided JIT - stacktrace during bootstrapping ? - => just use `-g1` flag ! ## 2021/09/21 ### escape analysis - focus on Julia-level optimizations ? - <https://hackmd.io/lCPPxuKxS3a7QLq1WO2DpA?view> - enhanced SROA - finalizer elision - how do we want to collect "mutation analysis" ? - encode it as `FieldEscapes` lattice - heap-to-stack optimization ? - we still need make design choices - (mainly) interactions with GC - I/O optimization - new collaborator :tada: ! - coordinate a new discussion document, meeting - make general development easier: testing and debugging <!-- 1. check if 2. assume allocation on stack 3. copy to heap (on something wrong) --> ### compiler-plugin - Keno: interested in a taint analysis ? - for Diffractor: smarter contribution detection (zero out derivative) - (credential leak detection) - How to "eliminate the duplication" ? - sysimg0: base/compiler (in `Base` module) - sysimg: add remaining `Base` parts on top of sysimg0 ? - => let's make `Core.Compiler` imports `Base` ! - define `Base`, then define `Compiler` - `show` and `getproperty` should be imported from `Core` ### JET-migration - v0.5 will be out on v1.7 release - abstractinterpret-based analysis part (i.e. things other than top-level analysis) is pretty good in shape at this moment ## 2021/08/31 - is there any other way to implement Cassette.jl ? - "better integrated" - with Cassette - abstract type signature: need to reason about entire dispatch table - resolve as runtime dispatch: force compilations ... - dispatch table - IDEA - **do not transform the original method source** - but, only adjust input and output - and run "some" inference - (propagate transformation context ... ?) - do we still have overdubbing mechanism ...? - put caches to the method cache of `overdub(args...)` - benefit over Cassette.jl ...? - how to handle cycles ? - re: inference limitation - compiler-plugin: we can apply transformation (and infer) abstract call signature - Cassette.jl: give up on abstract call site (because it relies on `@generated`) - method-matches: method match -> source code - need to hold the whole dispatch table - fundamental problems - ~~`@generated` function~~ - how to deal with abstract call site - autodiff - what's required: 1. we need primal 2. create dual - e.g. when we see `Any` ...? - re-infer at runtime ...? ```julia function foo(a) sin(a) + cos(a) # primal # cos(a) - sin(a) # shadow <= need to be inferred end ``` autodiff: append unknown code to known code Pack the dual code into a separate function (and lazily compile it ...?) --- - how to enable escape optimization as optional feature ... ? - setup a way to implement a way to execute program with different `AbstractInterpreter` - optimization manager - manages optimization passes for native pipeline - external consumer can stack pipeline to form customized optimization - optimization option ```julia= @with Pipeline(...) function foo(args...) sin(a) # this doesn't change: `sin` is `sin` end ``` ## 2021/08/18 ### Ideal inlining strategy ... ? - iterative inlining (continuously update cost model) - how to reason about the entire body ? => sort the costs at first - LLVM: "saved" ... ? - cost at before constant-prop': how we can save it ? ### `@noinfer` - when/how the users are supposed to use it ? - !!! it's really easy for the community to reuse existing information !!! ## 2021/08/17 ### `finalizer` elision Observe what could have `finalizer` - call `finalize` multi-times ? - currently - linear lookup for registered `finalize`s - there are edge-cases where registered finalizers can't be removed - tag if `finalize` is already called or not ### `current_exceptions` and `:jl_current_exception` Requires identifying locations where we can copy an object before any escape. Applicable especially to `ThrownEscape`, but applies to any escape (even FinalizerEscape). ```julia= function foo(a) r = Ref(a => a) some_closure() = ... try r.y catch err # impose all escape to `r` Base.@invokelatest maybe_current_exceptions() end return a end g() = foo(<escape-all?>) ``` - "copy" means recursive(semi-deep)-copy :+1: ```julia f() = invokelatest(rand((g, h, k, l)), Ref(:x)) g(x) = global haha = #stack?#(x) ? #copy#(x) : x h(@nospecialize x) = global haha = #stack?#(x) ? #copy#(x) : x k(x) = throw(#stack?#(x) ? #copy#(x) : x) l(x) = x.x = x ## HALP!!! ``` ## 2021/08/10 ### `@noinfer` idea - understanding `@nospecialize` * [used in `jl_compilation_sig`](https://github.com/JuliaLang/julia/blob/f442e4230a41d0d40f81346c1707f946f743cbcf/src/gf.c#L624-L633) * basically, just influence `specialize_method` in optimization, and changes how to create `:invoke` ? + e.g. if `@nospecialize`, argtype gonna be `Any` (or the declared type) + basically influence dispatches only, since inlining may still be worthwhile, and we want to forward all available information (e.g. constants and types) to the body of the method - `@noinfer` design * intervene into inference * change some of `argtypes` to annotated types in [`find_matching_methods`](https://github.com/JuliaLang/julia/blob/795935fd3a2d97b2f948cfb82a18da48743b622d/base/compiler/abstractinterpretation.jl#L258-L325) ? * Do we still need to do constant propagation on these arguments, or should this also block inlining? * Is `@noinfer` == `@noinline + @nospecialize`? * why constant-prop' is necessary for inlining ?: pushed into local cache * cut-off too much work (duplication) * in other word, constant-prop' shapes up the method body for inlining (mostly because dispatches can be simplified) ? * idea * limit the cache in local scope, but might not be enough * use the information from `@noinline` in order to cut off the cost of constantprop' - why the previous attempt to limit type information with `@nospecialize` was not successful ? ### `finalizer` elision - the conditions: at each return site, lookup for mutable objects (each SSAValue), and check their lifetime: * `GlobalEscape` (i.e. the object may live somewhere) : alive * `ReturnEscape::Nothing`: dead (unreachable) * `returnsite ∈ ReturnEscape::BitSet`: alive * `returnsite ∉ ReturnEscape::BitSet`: dead * simply ignore `ThrownEscape`: alive - add `FinalizerEscape` to the lattice - insert `finalize` call - (further optimization: inline the `finalize` call ?) => how to run inference in optimization - let's leave it for later - main prob: we have to mark `finalize` and tell GC not to call it multiple times - problem: call `finalize` multi-times ? - (slow) linear lookup for registered `finalize`s - ~~there are edge-cases where registered finalizers can't be removed~~ - => tag if `finalize` is already called or not ```julia= function foo() r = Ref(...) finalizer(f, r) r[] # unsafe, and `ThrownEscape` => `r` nothing # <= we can ignore `ThrownEscape` above ? end ``` ### New lattice design * implement this in Core.Compiler also? * makes it easier to add sub-properties * but, construction can be messy... * set up utility constructors * maybe use keyword arguments, and set default values * or "fake" kwargs if too slow? * `f(kws::NTuple{N,Symbol}, vals::Vararg{N, Any}) where N` * need to augment lattice to handle updating the escape information if the source of the value is unknown: e.g. `Base.Filesystem.pathsep() = path_separator` ```julia const x = Ref() function f() # = (x'.x = Ref()) y = Ref() x' = x # similar to: global x = x' = Ref() setfield(x', :x, y) ^ no-escape ^ no-escape ^ arg-escape(1), return-escape nothing end ``` ### How to integrate backward-analysis - how to combine forward and backward analyses ? - limited repeatition ? - how to preserve convergence ?