Skip to content

Commit

Permalink
optimizer: Julia-level escape analysis
Browse files Browse the repository at this point in the history
This commit ports [EscapeAnalysis.jl](https://github.com/aviatesk/EscapeAnalysis.jl) into Julia base.
You can find the documentation of this escape analysis at [this GitHub page](https://aviatesk.github.io/EscapeAnalysis.jl/dev/)[^1].

[^1]: The same documentation will be included into Julia's developer
      documentation by this commit.

This escape analysis will hopefully be an enabling technology for various
memory-related optimizations at Julia's high level compilation pipeline.
Possible target optimization includes mutable ϕ-node aware SROA,
`mutating_arrayfreeze` optimization (#42465), stack allocation of mutables,
dead array elimination, finalizer elision and so on[^2].

[^2]: It would be also interesting if LLVM-level optimizations can consume
      some IPO information derived by this escape analysis to broaden
      optimization possibilities.

The primary motivation for porting EA in this PR is to check its impact
on latency as well as to get feedbacks from a broader range of developers.
If there are no serious problems, EA will be used by #42465 for `mutating_arrayfreeze`
optimization for `ImmutableArray` construction.

This commit simply defines EA inside Julia base compiler and enables the
existing test suite with it. The plan is to validate EA's accuracy and
correctness and to check the latency impact by running EA before the
existing SROA pass and checking if it can detect all objects eliminated
by the SROA pass as "SROA-eliminatable".
  • Loading branch information
aviatesk committed Jan 22, 2022
1 parent 1db8b8f commit e343279
Show file tree
Hide file tree
Showing 20 changed files with 4,699 additions and 52 deletions.
3 changes: 3 additions & 0 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ include("util.jl")

include("asyncmap.jl")

include("compiler/ssair/EscapeAnalysis/EAUtils.jl")
using .EAUtils: code_escapes

# deprecated functions
include("deprecated.jl")

Expand Down
2 changes: 1 addition & 1 deletion base/compiler/bootstrap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let
fs = Any[
# we first create caches for the optimizer, because they contain many loop constructions
# and they're better to not run in interpreter even during bootstrapping
run_passes,
analyze_escapes, run_passes,
# then we create caches for inference entries
typeinf_ext, typeinf, typeinf_edge,
]
Expand Down
2 changes: 2 additions & 0 deletions base/compiler/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ ntuple(f, n) = (Any[f(i) for i = 1:n]...,)

# core docsystem
include("docs/core.jl")
import Core.Compiler.CoreDocs
Core.atdoc!(CoreDocs.docm)

# sorting
function sort end
Expand Down
86 changes: 45 additions & 41 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

#############
# constants #
#############

# The slot has uses that are not statically dominated by any assignment
# This is implied by `SLOT_USEDUNDEF`.
# If this is not set, all the uses are (statically) dominated by the defs.
# In particular, if a slot has `AssignedOnce && !StaticUndef`, it is an SSA.
const SLOT_STATICUNDEF = 1 # slot might be used before it is defined (structurally)
const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once
const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError
# const SLOT_CALLED = 64

# NOTE make sure to sync the flag definitions below with julia.h and `jl_code_info_set_ir` in method.c

const IR_FLAG_NULL = 0x00
# This statement is marked as @inbounds by user.
# Ff replaced by inlining, any contained boundschecks may be removed.
const IR_FLAG_INBOUNDS = 0x01 << 0
# This statement is marked as @inline by user
const IR_FLAG_INLINE = 0x01 << 1
# This statement is marked as @noinline by user
const IR_FLAG_NOINLINE = 0x01 << 2
const IR_FLAG_THROW_BLOCK = 0x01 << 3
# This statement may be removed if its result is unused. In particular it must
# thus be both pure and effect free.
const IR_FLAG_EFFECT_FREE = 0x01 << 4

# known to be always effect-free (in particular nothrow)
const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]

# known to be effect-free if the are nothrow
const _PURE_OR_ERROR_BUILTINS = [
fieldtype, apply_type, isa, UnionAll,
getfield, arrayref, const_arrayref, arraysize, isdefined, Core.sizeof,
Core.kwfunc, Core.ifelse, Core._typevar, (<:)
]

const TOP_TUPLE = GlobalRef(Core, :tuple)

#####################
# OptimizationState #
#####################
Expand Down Expand Up @@ -52,7 +92,10 @@ function inlining_policy(interp::AbstractInterpreter, @nospecialize(src), stmt_f
return nothing
end

function argextype end # imported by EscapeAnalysis
include("compiler/ssair/driver.jl")
using .EscapeAnalysis
import .EscapeAnalysis: EscapeState

mutable struct OptimizationState
linfo::MethodInstance
Expand Down Expand Up @@ -121,46 +164,6 @@ function ir_to_codeinf!(opt::OptimizationState)
return src
end

#############
# constants #
#############

# The slot has uses that are not statically dominated by any assignment
# This is implied by `SLOT_USEDUNDEF`.
# If this is not set, all the uses are (statically) dominated by the defs.
# In particular, if a slot has `AssignedOnce && !StaticUndef`, it is an SSA.
const SLOT_STATICUNDEF = 1 # slot might be used before it is defined (structurally)
const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once
const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError
# const SLOT_CALLED = 64

# NOTE make sure to sync the flag definitions below with julia.h and `jl_code_info_set_ir` in method.c

const IR_FLAG_NULL = 0x00
# This statement is marked as @inbounds by user.
# Ff replaced by inlining, any contained boundschecks may be removed.
const IR_FLAG_INBOUNDS = 0x01 << 0
# This statement is marked as @inline by user
const IR_FLAG_INLINE = 0x01 << 1
# This statement is marked as @noinline by user
const IR_FLAG_NOINLINE = 0x01 << 2
const IR_FLAG_THROW_BLOCK = 0x01 << 3
# This statement may be removed if its result is unused. In particular it must
# thus be both pure and effect free.
const IR_FLAG_EFFECT_FREE = 0x01 << 4

# known to be always effect-free (in particular nothrow)
const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]

# known to be effect-free if the are nothrow
const _PURE_OR_ERROR_BUILTINS = [
fieldtype, apply_type, isa, UnionAll,
getfield, arrayref, const_arrayref, arraysize, isdefined, Core.sizeof,
Core.kwfunc, Core.ifelse, Core._typevar, (<:)
]

const TOP_TUPLE = GlobalRef(Core, :tuple)

#########
# logic #
#########
Expand Down Expand Up @@ -514,7 +517,8 @@ function run_passes(ci::CodeInfo, sv::OptimizationState)
@timeit "Inlining" ir = ssa_inlining_pass!(ir, ir.linetable, sv.inlining, ci.propagate_inbounds)
# @timeit "verify 2" verify_ir(ir)
@timeit "compact 2" ir = compact!(ir)
@timeit "SROA" ir = sroa_pass!(ir)
nargs = let def = sv.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end
@timeit "SROA" ir = sroa_pass!(ir, nargs)
@timeit "ADCE" ir = adce_pass!(ir)
@timeit "type lift" ir = type_lift_pass!(ir)
@timeit "compact 3" ir = compact!(ir)
Expand Down
Loading

0 comments on commit e343279

Please sign in to comment.