Skip to content

Commit 757a5e0

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 f03e839 commit 757a5e0

File tree

18 files changed

+4427
-46
lines changed

18 files changed

+4427
-46
lines changed

base/Base.jl

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

394394
include("asyncmap.jl")
395395

396+
include("compiler/ssair/EscapeAnalysis/EAUtils.jl")
397+
using .EAUtils: code_escapes
398+
396399
# deprecated functions
397400
include("deprecated.jl")
398401

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
@@ -93,6 +93,8 @@ ntuple(f, n) = (Any[f(i) for i = 1:n]...,)
9393

9494
# core docsystem
9595
include("docs/core.jl")
96+
import Core.Compiler.CoreDocs
97+
Core.atdoc!(CoreDocs.docm)
9698

9799
# sorting
98100
function sort end

base/compiler/optimize.jl

+49-40
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
#########
@@ -518,6 +521,12 @@ function run_passes(ci::CodeInfo, sv::OptimizationState)
518521
@timeit "ADCE" ir = adce_pass!(ir)
519522
@timeit "type lift" ir = type_lift_pass!(ir)
520523
@timeit "compact 3" ir = compact!(ir)
524+
# TODO validate EA by comparing it to the existing SROA
525+
nargs = let def = sv.linfo.def
526+
isa(def, Method) ? Int(def.nargs) : 0
527+
end
528+
@timeit "EA" estate = analyze_escapes(ir, nargs)
529+
cache_escapes!(sv.linfo, estate, ir)
521530
if JLOptions().debug_level == 2
522531
@timeit "verify 3" (verify_ir(ir); verify_linetable(ir.linetable))
523532
end

0 commit comments

Comments
 (0)