Skip to content

Commit 007fa58

Browse files
committed
optimizer: Julia-level escape analysis
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".
1 parent 1db8b8f commit 007fa58

File tree

20 files changed

+4697
-52
lines changed

20 files changed

+4697
-52
lines changed

base/Base.jl

+3
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ include("util.jl")
397397

398398
include("asyncmap.jl")
399399

400+
include("compiler/ssair/EscapeAnalysis/EAUtils.jl")
401+
using .EAUtils: code_escapes
402+
400403
# deprecated functions
401404
include("deprecated.jl")
402405

base/compiler/bootstrap.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ let
1414
fs = Any[
1515
# we first create caches for the optimizer, because they contain many loop constructions
1616
# and they're better to not run in interpreter even during bootstrapping
17-
run_passes,
17+
analyze_escapes, run_passes,
1818
# then we create caches for inference entries
1919
typeinf_ext, typeinf, typeinf_edge,
2020
]

base/compiler/compiler.jl

+2
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ ntuple(f, n) = (Any[f(i) for i = 1:n]...,)
9595

9696
# core docsystem
9797
include("docs/core.jl")
98+
import Core.Compiler.CoreDocs
99+
Core.atdoc!(CoreDocs.docm)
98100

99101
# sorting
100102
function sort end

base/compiler/optimize.jl

+45-41
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,45 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3+
#############
4+
# constants #
5+
#############
6+
7+
# The slot has uses that are not statically dominated by any assignment
8+
# This is implied by `SLOT_USEDUNDEF`.
9+
# If this is not set, all the uses are (statically) dominated by the defs.
10+
# In particular, if a slot has `AssignedOnce && !StaticUndef`, it is an SSA.
11+
const SLOT_STATICUNDEF = 1 # slot might be used before it is defined (structurally)
12+
const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once
13+
const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError
14+
# const SLOT_CALLED = 64
15+
16+
# NOTE make sure to sync the flag definitions below with julia.h and `jl_code_info_set_ir` in method.c
17+
18+
const IR_FLAG_NULL = 0x00
19+
# This statement is marked as @inbounds by user.
20+
# Ff replaced by inlining, any contained boundschecks may be removed.
21+
const IR_FLAG_INBOUNDS = 0x01 << 0
22+
# This statement is marked as @inline by user
23+
const IR_FLAG_INLINE = 0x01 << 1
24+
# This statement is marked as @noinline by user
25+
const IR_FLAG_NOINLINE = 0x01 << 2
26+
const IR_FLAG_THROW_BLOCK = 0x01 << 3
27+
# This statement may be removed if its result is unused. In particular it must
28+
# thus be both pure and effect free.
29+
const IR_FLAG_EFFECT_FREE = 0x01 << 4
30+
31+
# known to be always effect-free (in particular nothrow)
32+
const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]
33+
34+
# known to be effect-free if the are nothrow
35+
const _PURE_OR_ERROR_BUILTINS = [
36+
fieldtype, apply_type, isa, UnionAll,
37+
getfield, arrayref, const_arrayref, arraysize, isdefined, Core.sizeof,
38+
Core.kwfunc, Core.ifelse, Core._typevar, (<:)
39+
]
40+
41+
const TOP_TUPLE = GlobalRef(Core, :tuple)
42+
343
#####################
444
# OptimizationState #
545
#####################
@@ -52,7 +92,10 @@ function inlining_policy(interp::AbstractInterpreter, @nospecialize(src), stmt_f
5292
return nothing
5393
end
5494

95+
function argextype end # imported by EscapeAnalysis
5596
include("compiler/ssair/driver.jl")
97+
using .EscapeAnalysis
98+
import .EscapeAnalysis: EscapeState
5699

57100
mutable struct OptimizationState
58101
linfo::MethodInstance
@@ -121,46 +164,6 @@ function ir_to_codeinf!(opt::OptimizationState)
121164
return src
122165
end
123166

124-
#############
125-
# constants #
126-
#############
127-
128-
# The slot has uses that are not statically dominated by any assignment
129-
# This is implied by `SLOT_USEDUNDEF`.
130-
# If this is not set, all the uses are (statically) dominated by the defs.
131-
# In particular, if a slot has `AssignedOnce && !StaticUndef`, it is an SSA.
132-
const SLOT_STATICUNDEF = 1 # slot might be used before it is defined (structurally)
133-
const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once
134-
const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError
135-
# const SLOT_CALLED = 64
136-
137-
# NOTE make sure to sync the flag definitions below with julia.h and `jl_code_info_set_ir` in method.c
138-
139-
const IR_FLAG_NULL = 0x00
140-
# This statement is marked as @inbounds by user.
141-
# Ff replaced by inlining, any contained boundschecks may be removed.
142-
const IR_FLAG_INBOUNDS = 0x01 << 0
143-
# This statement is marked as @inline by user
144-
const IR_FLAG_INLINE = 0x01 << 1
145-
# This statement is marked as @noinline by user
146-
const IR_FLAG_NOINLINE = 0x01 << 2
147-
const IR_FLAG_THROW_BLOCK = 0x01 << 3
148-
# This statement may be removed if its result is unused. In particular it must
149-
# thus be both pure and effect free.
150-
const IR_FLAG_EFFECT_FREE = 0x01 << 4
151-
152-
# known to be always effect-free (in particular nothrow)
153-
const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]
154-
155-
# known to be effect-free if the are nothrow
156-
const _PURE_OR_ERROR_BUILTINS = [
157-
fieldtype, apply_type, isa, UnionAll,
158-
getfield, arrayref, const_arrayref, arraysize, isdefined, Core.sizeof,
159-
Core.kwfunc, Core.ifelse, Core._typevar, (<:)
160-
]
161-
162-
const TOP_TUPLE = GlobalRef(Core, :tuple)
163-
164167
#########
165168
# logic #
166169
#########
@@ -514,7 +517,8 @@ function run_passes(ci::CodeInfo, sv::OptimizationState)
514517
@timeit "Inlining" ir = ssa_inlining_pass!(ir, ir.linetable, sv.inlining, ci.propagate_inbounds)
515518
# @timeit "verify 2" verify_ir(ir)
516519
@timeit "compact 2" ir = compact!(ir)
517-
@timeit "SROA" ir = sroa_pass!(ir)
520+
nargs = let def = sv.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end
521+
@timeit "SROA" ir = sroa_pass!(ir, nargs)
518522
@timeit "ADCE" ir = adce_pass!(ir)
519523
@timeit "type lift" ir = type_lift_pass!(ir)
520524
@timeit "compact 3" ir = compact!(ir)

0 commit comments

Comments
 (0)