# 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 ?