Skip to content

Commit c675a80

Browse files
committed
EA: perform analysis once for post-optimization IR, and remove IPO EA
Following the discussions and changes in #50805, we now consider post-inlining IR as IPO-valid. Revisiting EA, I've realized that running EA twice—once for computing IPO-valid escape cache and once for local optimization analysis—is redundant. This commit streamlines the EA process to perform the analysis just once on post-optimization IR, and caches that result. This change also removes all interprocedural EA code, which had significant overlap with inlining code.
1 parent d9a9be8 commit c675a80

File tree

9 files changed

+389
-788
lines changed

9 files changed

+389
-788
lines changed

base/compiler/bootstrap.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ time() = ccall(:jl_clock_now, Float64, ())
99

1010
let interp = NativeInterpreter()
1111

12-
# analyze_escapes_tt = Tuple{typeof(analyze_escapes), IRCode, Int, Bool, typeof(null_escape_cache)}
12+
# analyze_escapes_tt = Tuple{typeof(analyze_escapes), IRCode, Int, Bool, TODO}
1313
fs = Any[
1414
# we first create caches for the optimizer, because they contain many loop constructions
1515
# and they're better to not run in interpreter even during bootstrapping

base/compiler/optimize.jl

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -788,32 +788,6 @@ function optimize(interp::AbstractInterpreter, opt::OptimizationState, caller::I
788788
return finish(interp, opt, ir, caller)
789789
end
790790

791-
using .EscapeAnalysis
792-
import .EscapeAnalysis: EscapeState, ArgEscapeCache, is_ipo_profitable
793-
794-
"""
795-
cache_escapes!(caller::InferenceResult, estate::EscapeState)
796-
797-
Transforms escape information of call arguments of `caller`,
798-
and then caches it into a global cache for later interprocedural propagation.
799-
"""
800-
cache_escapes!(caller::InferenceResult, estate::EscapeState) =
801-
caller.argescapes = ArgEscapeCache(estate)
802-
803-
function ipo_escape_cache(mi_cache::MICache) where MICache
804-
return function (linfo::Union{InferenceResult,MethodInstance})
805-
if isa(linfo, InferenceResult)
806-
argescapes = linfo.argescapes
807-
else
808-
codeinst = get(mi_cache, linfo, nothing)
809-
isa(codeinst, CodeInstance) || return nothing
810-
argescapes = codeinst.argescapes
811-
end
812-
return argescapes !== nothing ? argescapes::ArgEscapeCache : nothing
813-
end
814-
end
815-
null_escape_cache(linfo::Union{InferenceResult,MethodInstance}) = nothing
816-
817791
macro pass(name, expr)
818792
optimize_until = esc(:optimize_until)
819793
stage = esc(:__stage__)

base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl

Lines changed: 58 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ else
4040
end
4141

4242
const AInfo = IdSet{Any}
43-
const LivenessSet = BitSet
4443
const 𝕃ₒ = SimpleInferenceLattice.instance
4544

4645
"""
@@ -87,16 +86,16 @@ An abstract state will be initialized with the bottom(-like) elements:
8786
struct EscapeInfo
8887
Analyzed::Bool
8988
ReturnEscape::Bool
90-
ThrownEscape::LivenessSet
89+
ThrownEscape::BitSet
9190
AliasInfo #::Union{IndexableFields,IndexableElements,Unindexable,Bool}
92-
Liveness::LivenessSet
91+
Liveness::BitSet
9392

9493
function EscapeInfo(
9594
Analyzed::Bool,
9695
ReturnEscape::Bool,
97-
ThrownEscape::LivenessSet,
96+
ThrownEscape::BitSet,
9897
AliasInfo#=::Union{IndexableFields,IndexableElements,Unindexable,Bool}=#,
99-
Liveness::LivenessSet)
98+
Liveness::BitSet)
10099
@nospecialize AliasInfo
101100
return new(
102101
Analyzed,
@@ -112,8 +111,8 @@ struct EscapeInfo
112111
AliasInfo#=::Union{IndexableFields,IndexableElements,Unindexable,Bool}=# = x.AliasInfo;
113112
Analyzed::Bool = x.Analyzed,
114113
ReturnEscape::Bool = x.ReturnEscape,
115-
ThrownEscape::LivenessSet = x.ThrownEscape,
116-
Liveness::LivenessSet = x.Liveness)
114+
ThrownEscape::BitSet = x.ThrownEscape,
115+
Liveness::BitSet = x.Liveness)
117116
@nospecialize AliasInfo
118117
return new(
119118
Analyzed,
@@ -126,24 +125,24 @@ end
126125

127126
# precomputed default values in order to eliminate computations at each callsite
128127

129-
const BOT_THROWN_ESCAPE = LivenessSet()
128+
const BOT_THROWN_ESCAPE = BitSet()
130129
# NOTE the lattice operations should try to avoid actual set computations on this top value,
131-
# and e.g. LivenessSet(0:1000000) should also work without incurring excessive computations
132-
const TOP_THROWN_ESCAPE = LivenessSet(-1)
130+
# and e.g. BitSet(0:1000000) should also work without incurring excessive computations
131+
const TOP_THROWN_ESCAPE = BitSet(-1)
133132

134-
const BOT_LIVENESS = LivenessSet()
133+
const BOT_LIVENESS = BitSet()
135134
# NOTE the lattice operations should try to avoid actual set computations on this top value,
136-
# and e.g. LivenessSet(0:1000000) should also work without incurring excessive computations
137-
const TOP_LIVENESS = LivenessSet(-1:0)
138-
const ARG_LIVENESS = LivenessSet(0)
135+
# and e.g. BitSet(0:1000000) should also work without incurring excessive computations
136+
const TOP_LIVENESS = BitSet(-1:0)
137+
const ARG_LIVENESS = BitSet(0)
139138

140139
# the constructors
141140
NotAnalyzed() = EscapeInfo(false, false, BOT_THROWN_ESCAPE, false, BOT_LIVENESS) # not formally part of the lattice
142141
NoEscape() = EscapeInfo(true, false, BOT_THROWN_ESCAPE, false, BOT_LIVENESS)
143142
ArgEscape() = EscapeInfo(true, false, BOT_THROWN_ESCAPE, true, ARG_LIVENESS)
144-
ReturnEscape(pc::Int) = EscapeInfo(true, true, BOT_THROWN_ESCAPE, false, LivenessSet(pc))
143+
ReturnEscape(pc::Int) = EscapeInfo(true, true, BOT_THROWN_ESCAPE, false, BitSet(pc))
145144
AllReturnEscape() = EscapeInfo(true, true, BOT_THROWN_ESCAPE, false, TOP_LIVENESS)
146-
ThrownEscape(pc::Int) = EscapeInfo(true, false, LivenessSet(pc), false, BOT_LIVENESS)
145+
ThrownEscape(pc::Int) = EscapeInfo(true, false, BitSet(pc), false, BOT_LIVENESS)
147146
AllEscape() = EscapeInfo(true, true, TOP_THROWN_ESCAPE, true, TOP_LIVENESS)
148147

149148
const ⊥, ⊤ = NotAnalyzed(), AllEscape()
@@ -626,28 +625,26 @@ struct LivenessChange <: Change
626625
end
627626
const Changes = Vector{Change}
628627

629-
struct AnalysisState{T<:Callable}
628+
struct AnalysisState{T}
630629
ir::IRCode
631630
estate::EscapeState
632631
changes::Changes
633632
get_escape_cache::T
634633
end
635634

636635
"""
637-
analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, get_escape_cache::Callable)
638-
-> estate::EscapeState
636+
analyze_escapes(ir::IRCode, nargs::Int, get_escape_cache) -> estate::EscapeState
639637
640638
Analyzes escape information in `ir`:
641639
- `nargs`: the number of actual arguments of the analyzed call
642-
- `call_resolved`: if interprocedural calls are already resolved by `ssa_inlining_pass!`
643-
- `get_escape_cache(::Union{InferenceResult,MethodInstance}) -> Union{Nothing,ArgEscapeCache}`:
640+
- `get_escape_cache(::MethodInstance) -> Union{Nothing,ArgEscapeCache}`:
644641
retrieves cached argument escape information
645642
"""
646-
function analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, get_escape_cache::T) where T<:Callable
643+
function analyze_escapes(ir::IRCode, nargs::Int, get_escape_cache)
647644
stmts = ir.stmts
648645
nstmts = length(stmts) + length(ir.new_nodes.stmts)
649646

650-
tryregions, arrayinfo, callinfo = compute_frameinfo(ir, call_resolved)
647+
tryregions, arrayinfo = compute_frameinfo(ir)
651648
estate = EscapeState(nargs, nstmts, arrayinfo)
652649
changes = Changes() # keeps changes that happen at current statement
653650
astate = AnalysisState(ir, estate, changes, get_escape_cache)
@@ -663,11 +660,7 @@ function analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, get_escape
663660
if isa(stmt, Expr)
664661
head = stmt.head
665662
if head === :call
666-
if callinfo !== nothing
667-
escape_call!(astate, pc, stmt.args, callinfo)
668-
else
669-
escape_call!(astate, pc, stmt.args)
670-
end
663+
escape_call!(astate, pc, stmt.args)
671664
elseif head === :invoke
672665
escape_invoke!(astate, pc, stmt.args)
673666
elseif head === :new || head === :splatnew
@@ -744,41 +737,25 @@ function analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, get_escape
744737
end
745738

746739
"""
747-
compute_frameinfo(ir::IRCode, call_resolved::Bool) -> (tryregions, arrayinfo, callinfo)
740+
compute_frameinfo(ir::IRCode) -> (tryregions, arrayinfo)
748741
749742
A preparatory linear scan before the escape analysis on `ir` to find:
750743
- `tryregions::Union{Nothing,Vector{UnitRange{Int}}}`: regions in which potential `throw`s can be caught (used by `escape_exception!`)
751744
- `arrayinfo::Union{Nothing,IdDict{Int,Vector{Int}}}`: array allocations whose dimensions are known precisely (with some very simple local analysis)
752-
- `callinfo::`: when `!call_resolved`, `compute_frameinfo` additionally returns `callinfo::Vector{Union{MethodInstance,InferenceResult}}`,
753-
which contains information about statically resolved callsites.
754-
The inliner will use essentially equivalent interprocedural information to inline callees as well as resolve static callsites,
755-
this additional information won't be required when analyzing post-inlining IR.
756745
757746
!!! note
758747
This array dimension analysis to compute `arrayinfo` is very local and doesn't account
759748
for flow-sensitivity nor complex aliasing.
760749
Ideally this dimension analysis should be done as a part of type inference that
761750
propagates array dimensions in a flow sensitive way.
762751
"""
763-
function compute_frameinfo(ir::IRCode, call_resolved::Bool)
752+
function compute_frameinfo(ir::IRCode)
764753
nstmts, nnewnodes = length(ir.stmts), length(ir.new_nodes.stmts)
765754
tryregions, arrayinfo = nothing, nothing
766-
if !call_resolved
767-
callinfo = Vector{Any}(undef, nstmts+nnewnodes)
768-
else
769-
callinfo = nothing
770-
end
771755
for idx in 1:nstmts+nnewnodes
772756
inst = ir[SSAValue(idx)]
773757
stmt = inst[:stmt]
774-
if !call_resolved
775-
# TODO don't call `check_effect_free!` in the inlinear
776-
check_effect_free!(ir, idx, stmt, inst[:type], 𝕃ₒ)
777-
end
778-
if callinfo !== nothing && isexpr(stmt, :call)
779-
# TODO: pass effects here
780-
callinfo[idx] = resolve_call(ir, stmt, inst[:info])
781-
elseif isexpr(stmt, :enter)
758+
if isexpr(stmt, :enter)
782759
@assert idx nstmts "try/catch inside new_nodes unsupported"
783760
tryregions === nothing && (tryregions = UnitRange{Int}[])
784761
leave_block = stmt.args[1]::Int
@@ -851,14 +828,7 @@ function compute_frameinfo(ir::IRCode, call_resolved::Bool)
851828
end
852829
@label next_stmt
853830
end
854-
return tryregions, arrayinfo, callinfo
855-
end
856-
857-
# define resolve_call
858-
if _TOP_MOD === Core.Compiler
859-
include("compiler/ssair/EscapeAnalysis/interprocedural.jl")
860-
else
861-
include("interprocedural.jl")
831+
return tryregions, arrayinfo
862832
end
863833

864834
# propagate changes, and check convergence
@@ -906,7 +876,7 @@ end
906876
return false
907877
end
908878

909-
# propagate Liveness changes separately in order to avoid constructing too many LivenessSet
879+
# propagate Liveness changes separately in order to avoid constructing too many BitSet
910880
@inline function propagate_liveness_change!(estate::EscapeState, change::LivenessChange)
911881
(; xidx, livepc) = change
912882
info = estate.escapes[xidx]
@@ -1149,21 +1119,17 @@ escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}) =
11491119
escape_invoke!(astate, pc, args, first(args)::MethodInstance, 2)
11501120

11511121
function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any},
1152-
linfo::Linfo, first_idx::Int, last_idx::Int = length(args))
1153-
if isa(linfo, InferenceResult)
1154-
cache = astate.get_escape_cache(linfo)
1155-
linfo = linfo.linfo
1156-
else
1157-
cache = astate.get_escape_cache(linfo)
1158-
end
1122+
mi::MethodInstance, first_idx::Int, last_idx::Int = length(args))
1123+
# TODO inspect `astate.ir.stmts[pc][:info]` and use const-prop'ed `InferenceResult` if available
1124+
cache = astate.get_escape_cache(mi)
11591125
if cache === nothing
11601126
return add_conservative_changes!(astate, pc, args, 2)
11611127
else
11621128
cache = cache::ArgEscapeCache
11631129
end
11641130
ret = SSAValue(pc)
11651131
retinfo = astate.estate[ret] # escape information imposed on the call statement
1166-
method = linfo.def::Method
1132+
method = mi.def::Method
11671133
nargs = Int(method.nargs)
11681134
for (i, argidx) in enumerate(first_idx:last_idx)
11691135
arg = args[argidx]
@@ -1172,18 +1138,14 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any},
11721138
# COMBAK will this be invalid once we take alias information into account?
11731139
i = nargs
11741140
end
1175-
arginfo = cache.argescapes[i]
1176-
info = from_interprocedural(arginfo, pc)
1177-
if has_return_escape(arginfo)
1178-
# if this argument can be "returned", in addition to propagating
1179-
# the escape information imposed on this call argument within the callee,
1180-
# we should also account for possible aliasing of this argument and the returned value
1181-
add_escape_change!(astate, arg, info)
1141+
argescape = cache.argescapes[i]
1142+
info = from_interprocedural(argescape, pc)
1143+
# propagate the escape information imposed on this call argument by the callee
1144+
add_escape_change!(astate, arg, info)
1145+
if has_return_escape(argescape)
1146+
# if this argument can be "returned", we should also account for possible
1147+
# aliasing between this argument and the returned value
11821148
add_alias_change!(astate, ret, arg)
1183-
else
1184-
# if this is simply passed as the call argument, we can just propagate
1185-
# the escape information imposed on this call argument within the callee
1186-
add_escape_change!(astate, arg, info)
11871149
end
11881150
end
11891151
for (; aidx, bidx) in cache.argaliases
@@ -1194,24 +1156,22 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any},
11941156
end
11951157

11961158
"""
1197-
from_interprocedural(arginfo::ArgEscapeInfo, pc::Int) -> x::EscapeInfo
1159+
from_interprocedural(argescape::ArgEscapeInfo, pc::Int) -> x::EscapeInfo
11981160
1199-
Reinterprets the escape information imposed on the call argument which is cached as `arginfo`
1161+
Reinterprets the escape information imposed on the call argument which is cached as `argescape`
12001162
in the context of the caller frame, where `pc` is the SSA statement number of the return value.
12011163
"""
1202-
function from_interprocedural(arginfo::ArgEscapeInfo, pc::Int)
1203-
has_all_escape(arginfo) && return
1204-
1205-
ThrownEscape = has_thrown_escape(arginfo) ? LivenessSet(pc) : BOT_THROWN_ESCAPE
1206-
1207-
return EscapeInfo(
1208-
#=Analyzed=#true, #=ReturnEscape=#false, ThrownEscape,
1209-
# FIXME implement interprocedural memory effect-analysis
1210-
# currently, this essentially disables the entire field analysis
1211-
# it might be okay from the SROA point of view, since we can't remove the allocation
1212-
# as far as it's passed to a callee anyway, but still we may want some field analysis
1213-
# for e.g. stack allocation or some other IPO optimizations
1214-
#=AliasInfo=#true, #=Liveness=#LivenessSet(pc))
1164+
function from_interprocedural(argescape::ArgEscapeInfo, pc::Int)
1165+
has_all_escape(argescape) && return
1166+
ThrownEscape = has_thrown_escape(argescape) ? BitSet(pc) : BOT_THROWN_ESCAPE
1167+
# TODO implement interprocedural memory effect-analysis:
1168+
# currently, this essentially disables the entire field analysis–it might be okay from
1169+
# the SROA point of view, since we can't remove the allocation as far as it's passed to
1170+
# a callee anyway, but still we may want some field analysis for e.g. stack allocation
1171+
# or some other IPO optimizations
1172+
AliasInfo = true
1173+
Liveness = BitSet(pc)
1174+
return EscapeInfo(#=Analyzed=#true, #=ReturnEscape=#false, ThrownEscape, AliasInfo, Liveness)
12151175
end
12161176

12171177
# escape every argument `(args[6:length(args[3])])` and the name `args[1]`
@@ -1270,27 +1230,6 @@ end
12701230

12711231
normalize(@nospecialize x) = isa(x, QuoteNode) ? x.value : x
12721232

1273-
function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any}, callinfo::Vector{Any})
1274-
info = callinfo[pc]
1275-
if isa(info, Bool)
1276-
info && return # known to be no escape
1277-
# now cascade to the builtin handling
1278-
escape_call!(astate, pc, args)
1279-
return
1280-
elseif isa(info, EACallInfo)
1281-
for linfo in info.linfos
1282-
escape_invoke!(astate, pc, args, linfo, 1)
1283-
end
1284-
# accounts for a potential escape via MethodError
1285-
info.nothrow || add_thrown_escapes!(astate, pc, args)
1286-
return
1287-
else
1288-
@assert info === missing
1289-
# if this call couldn't be analyzed, escape it conservatively
1290-
add_conservative_changes!(astate, pc, args)
1291-
end
1292-
end
1293-
12941233
function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any})
12951234
ir = astate.ir
12961235
ft = argextype(first(args), ir, ir.sptypes, ir.argtypes)
@@ -1331,16 +1270,17 @@ function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any})
13311270
end
13321271
end
13331272

1334-
escape_builtin!(@nospecialize(f), _...) = return missing
1273+
escape_builtin!(@nospecialize(f), _...) = missing
13351274

13361275
# safe builtins
1337-
escape_builtin!(::typeof(isa), _...) = return false
1338-
escape_builtin!(::typeof(typeof), _...) = return false
1339-
escape_builtin!(::typeof(sizeof), _...) = return false
1340-
escape_builtin!(::typeof(===), _...) = return false
1276+
escape_builtin!(::typeof(isa), _...) = false
1277+
escape_builtin!(::typeof(typeof), _...) = false
1278+
escape_builtin!(::typeof(sizeof), _...) = false
1279+
escape_builtin!(::typeof(===), _...) = false
1280+
escape_builtin!(::typeof(Core.donotdelete), _...) = false
13411281
# not really safe, but `ThrownEscape` will be imposed later
1342-
escape_builtin!(::typeof(isdefined), _...) = return false
1343-
escape_builtin!(::typeof(throw), _...) = return false
1282+
escape_builtin!(::typeof(isdefined), _...) = false
1283+
escape_builtin!(::typeof(throw), _...) = false
13441284

13451285
function escape_builtin!(::typeof(ifelse), astate::AnalysisState, pc::Int, args::Vector{Any})
13461286
length(args) == 4 || return false

0 commit comments

Comments
 (0)