---
# System prepended metadata

title: Escape Analysis MTG @ 2021/06/07

---

# Escape Analysis MTG @ 2021/06/07

# From abstract interpretation point of view

## Current States of pointer/alias analysis

Some of pointer/alias analysis is already performed, but very limitedly.

Within our current lattice implementation, "must-alias" information is essentially same as object identity,
which can be only exploited for `Const` objects,
and so exact pointer value information is known only for `Const` and `PartialStruct`.
```julia
julia> # constant folding
       struct Foo
           foo
       end

julia> code_typed(; optimize=false) do
           f1 = Foo(1)            # Const(Foo(1))
           f2 = f1                # Const(Foo(1))
       
           # alias information
           r1 = f1 === f2         # Const(true), i.e. "must-alias"
           r2 = f1.foo === f2.foo # (Const(1), Const(1)), pointers are fully known
       
           return r1, r2
       end |> first
CodeInfo(
1 ─      (f1 = Main.Foo(1))::Core.Const(Foo(1))
│        (f2 = f1::Core.Const(Foo(1)))::Core.Const(Foo(1))
│        (r1 = f1::Core.Const(Foo(1)) === f2::Core.Const(Foo(1)))::Core.Const(true)
│   %4 = Base.getproperty(f1::Core.Const(Foo(1)), :foo)::Core.Const(1)
│   %5 = Base.getproperty(f2::Core.Const(Foo(1)), :foo)::Core.Const(1)
│        (r2 = %4 === %5)::Core.Const(true)
│   %7 = Core.tuple(r1::Core.Const(true), r2::Core.Const(true))::Core.Const((true, true))
└──      return %7
) => Tuple{Bool, Bool}

julia> # partial constant folding
       struct FooPartial
           foo1
           foo2
       end

julia> code_typed((Any,); optimize=false) do foo2
           f1 = FooPartial(1, foo2)  # PartialStruct(FooPartial, Any[Const(1), Any])
           f2 = f1                   # PartialStruct(FooPartial, Any[Const(1), Any])
       
           # alias information
           r1  = f1      === f2      # Bool, not unknown
           r21 = f1.foo1 === f2.foo1 # Const(true), pointers can be partially known
           r22 = f1.foo2 === f2.foo2 # Bool, not known
           @assert f1.foo2 === f2.foo2
           r23 = f1.foo2 === f2.foo2 # Bool, not known even if we manually asserted
       
           return r1, r21, r22, r23
       end |> first
CodeInfo(
1 ─       Core.NewvarNode(:(r23))::Any
│         (f1 = Main.FooPartial(1, foo2))::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any])
│         (f2 = f1::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]))::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any])
│         (r1 = f1::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]) === f2::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]))::Bool
│   %5  = Base.getproperty(f1::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]), :foo1)::Core.Const(1)
│   %6  = Base.getproperty(f2::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]), :foo1)::Core.Const(1)
│         (r21 = %5 === %6)::Core.Const(true)
│   %8  = Base.getproperty(f1::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]), :foo2)::Any
│   %9  = Base.getproperty(f2::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]), :foo2)::Any
│         (r22 = %8 === %9)::Bool
│   %11 = Base.getproperty(f1::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]), :foo2)::Any
│   %12 = Base.getproperty(f2::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]), :foo2)::Any
│   %13 = (%11 === %12)::Bool
└──       goto #3 if not %13
2 ─       goto #4
3 ─ %16 = Base.AssertionError("f1.foo2 === f2.foo2")::Any
└──       Base.throw(%16)::Union{}
4 ┄ %18 = Base.getproperty(f1::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]), :foo2)::Any
│   %19 = Base.getproperty(f2::Core.PartialStruct(FooPartial, Any[Core.Const(1), Any]), :foo2)::Any
│         (r23 = %18 === %19)::Bool
│   %21 = Core.tuple(r1, r21::Core.Const(true), r22, r23)::Core.PartialStruct(NTuple{4, Bool}, Any[Bool, Core.Const(true), Bool, Bool])
└──       return %21
) => NTuple{4, Bool}
```



Currently `Const` and `PartialStruct` don't handle `mutable struct` objects because we don't track scope of pointers (this is an escape information ?)
(especially, handled [here](https://github.com/JuliaLang/julia/blob/0a246488f87203ffe11f60f3a506b9db83298b6c/base/compiler/abstractinterpretation.jl#L1431)).
```julia
julia> mutable struct FooMutable
           foo
       end

julia> code_typed(; optimize=false) do
           f1 = FooMutable(1)     # FooMutable
           f2 = f1                # FooMutable
       
           # alias information
           r1 = f1 === f2         # Bool, not known
           r2 = f1.foo === f2.foo # Bool, not known
       
           return r1, r2
       end |> first
CodeInfo(
1 ─      (f1 = Main.FooMutable(1))::FooMutable
│        (f2 = f1)::FooMutable
│        (r1 = f1 === f2)::Bool
│   %4 = Base.getproperty(f1, :foo)::Any
│   %5 = Base.getproperty(f2, :foo)::Any
│        (r2 = %4 === %5)::Bool
│   %7 = Core.tuple(r1, r2)::Tuple{Bool, Bool}
└──      return %7
) => Tuple{Bool, Bool}
```



## Improvement ideas

### Local pointer analysis improvement

- We can introduce new lattice like `ConstrainableField` and `ConstainableStruct`,
  to improve local pointer information a bit more
E.g.:
```julia
julia> struct Foo
           foo
       end

julia> code_typed((Foo,); optimize=false) do foo
           # foo :: ConstainableStruct(Foo, Any[])
           if isa(foo.foo, Int) # foo.foo :: ConstrainableField(SlotNumber(2), :foo, Int)
               # foo :: ConstainableStruct(Foo, Any[(:foo, Int)])
               return foo.foo # :: Int
           end
       end |> first
CodeInfo(
1 ─ %1 = Base.getproperty(foo, :foo)::Any
│   %2 = (%1 isa Main.Int)::Bool
└──      goto #3 if not %2
2 ─ %4 = Base.getproperty(foo, :foo)::Any
└──      return %4
3 ─      return nothing
) => Any
```



- Still limited to `struct`, because we lack escape information
- probably we can't use these lattices for "must-alias" information

## Escape analysis

Once escape analysis is landed, we may be able to enable (partial) constant-folding on `mutable struct`s
and do something like `ConstrainableField/Struct` stuff for them.
```julia
julia> mutable struct FooMutable
           foo
       end

julia> code_typed(; optimize=false) do
           # now we know both `f1` and `f2` doesn't escape this scope, we can constant-fold them
       
           f1 = FooMutable(1)     # Const(FooMutable(1))
           f2 = f1                # Const(FooMutable(1))
       
           # alias information
           r1 = f1 === f2         # Const(true)
           r2 = f1.foo === f2.foo # Const(true)
       
           return r1, r2
       end |> first
CodeInfo(
1 ─      (f1 = Main.FooMutable(1))::FooMutable
│        (f2 = f1)::FooMutable
│        (r1 = f1 === f2)::Bool
│   %4 = Base.getproperty(f1, :foo)::Any
│   %5 = Base.getproperty(f2, :foo)::Any
│        (r2 = %4 === %5)::Bool
│   %7 = Core.tuple(r1, r2)::Tuple{Bool, Bool}
└──      return %7
) => Tuple{Bool, Bool}
```

Considerations:
- this motivates doing escape analysis _during_ inference
- but escape analysis is probably better to be implemented as a post-inference process, so we may not be able to exploit the information during abstract interpret..
  * but escape information might be hard to drive without type information


# Questions

- is it reasonable to think of usages of escape information for optimizaitons, _fully on Julia-level_ ?
- some possible optimizations (from [wiki](https://en.wikipedia.org/wiki/Escape_analysis)):
  * **scalar replacement:**: will be possible as an optimization pass
  * **heap allocations to stack allocations**: how to enable this on Julia level ? introduce meta label ?
  * synchronization elision: almost sure this is out of our scope for the time being

# Discussions

- target `mutable struct`s
  * finalizers ?
  * no escape, then unbox ?
- recursive calls
  * self-recursive isn't a probrem
  * mutually-recursive is an evil
  * but the complicated cycle handling is only necessary for inference, and for a separated escape analysis pass, we can just write a simple code to handle cycles ?
* two different sources of alias approach
  * find alias bindings: by abstract interpretation
  * use allocation sites: what LLVM does it already