40
40
end
41
41
42
42
const AInfo = IdSet{Any}
43
- const LivenessSet = BitSet
44
43
const 𝕃ₒ = SimpleInferenceLattice. instance
45
44
46
45
"""
@@ -87,16 +86,16 @@ An abstract state will be initialized with the bottom(-like) elements:
87
86
struct EscapeInfo
88
87
Analyzed:: Bool
89
88
ReturnEscape:: Bool
90
- ThrownEscape:: LivenessSet
89
+ ThrownEscape:: BitSet
91
90
AliasInfo # ::Union{IndexableFields,IndexableElements,Unindexable,Bool}
92
- Liveness:: LivenessSet
91
+ Liveness:: BitSet
93
92
94
93
function EscapeInfo (
95
94
Analyzed:: Bool ,
96
95
ReturnEscape:: Bool ,
97
- ThrownEscape:: LivenessSet ,
96
+ ThrownEscape:: BitSet ,
98
97
AliasInfo#= ::Union{IndexableFields,IndexableElements,Unindexable,Bool}=# ,
99
- Liveness:: LivenessSet )
98
+ Liveness:: BitSet )
100
99
@nospecialize AliasInfo
101
100
return new (
102
101
Analyzed,
@@ -112,8 +111,8 @@ struct EscapeInfo
112
111
AliasInfo#= ::Union{IndexableFields,IndexableElements,Unindexable,Bool}=# = x. AliasInfo;
113
112
Analyzed:: Bool = x. Analyzed,
114
113
ReturnEscape:: Bool = x. ReturnEscape,
115
- ThrownEscape:: LivenessSet = x. ThrownEscape,
116
- Liveness:: LivenessSet = x. Liveness)
114
+ ThrownEscape:: BitSet = x. ThrownEscape,
115
+ Liveness:: BitSet = x. Liveness)
117
116
@nospecialize AliasInfo
118
117
return new (
119
118
Analyzed,
@@ -126,24 +125,24 @@ end
126
125
127
126
# precomputed default values in order to eliminate computations at each callsite
128
127
129
- const BOT_THROWN_ESCAPE = LivenessSet ()
128
+ const BOT_THROWN_ESCAPE = BitSet ()
130
129
# 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 )
133
132
134
- const BOT_LIVENESS = LivenessSet ()
133
+ const BOT_LIVENESS = BitSet ()
135
134
# 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 )
139
138
140
139
# the constructors
141
140
NotAnalyzed () = EscapeInfo (false , false , BOT_THROWN_ESCAPE, false , BOT_LIVENESS) # not formally part of the lattice
142
141
NoEscape () = EscapeInfo (true , false , BOT_THROWN_ESCAPE, false , BOT_LIVENESS)
143
142
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))
145
144
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)
147
146
AllEscape () = EscapeInfo (true , true , TOP_THROWN_ESCAPE, true , TOP_LIVENESS)
148
147
149
148
const ⊥, ⊤ = NotAnalyzed (), AllEscape ()
@@ -626,28 +625,26 @@ struct LivenessChange <: Change
626
625
end
627
626
const Changes = Vector{Change}
628
627
629
- struct AnalysisState{T<: Callable }
628
+ struct AnalysisState{T}
630
629
ir:: IRCode
631
630
estate:: EscapeState
632
631
changes:: Changes
633
632
get_escape_cache:: T
634
633
end
635
634
636
635
"""
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
639
637
640
638
Analyzes escape information in `ir`:
641
639
- `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}`:
644
641
retrieves cached argument escape information
645
642
"""
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)
647
644
stmts = ir. stmts
648
645
nstmts = length (stmts) + length (ir. new_nodes. stmts)
649
646
650
- tryregions, arrayinfo, callinfo = compute_frameinfo (ir, call_resolved )
647
+ tryregions, arrayinfo = compute_frameinfo (ir)
651
648
estate = EscapeState (nargs, nstmts, arrayinfo)
652
649
changes = Changes () # keeps changes that happen at current statement
653
650
astate = AnalysisState (ir, estate, changes, get_escape_cache)
@@ -663,11 +660,7 @@ function analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, get_escape
663
660
if isa (stmt, Expr)
664
661
head = stmt. head
665
662
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)
671
664
elseif head === :invoke
672
665
escape_invoke! (astate, pc, stmt. args)
673
666
elseif head === :new || head === :splatnew
@@ -744,41 +737,25 @@ function analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, get_escape
744
737
end
745
738
746
739
"""
747
- compute_frameinfo(ir::IRCode, call_resolved::Bool ) -> (tryregions, arrayinfo, callinfo )
740
+ compute_frameinfo(ir::IRCode) -> (tryregions, arrayinfo)
748
741
749
742
A preparatory linear scan before the escape analysis on `ir` to find:
750
743
- `tryregions::Union{Nothing,Vector{UnitRange{Int}}}`: regions in which potential `throw`s can be caught (used by `escape_exception!`)
751
744
- `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.
756
745
757
746
!!! note
758
747
This array dimension analysis to compute `arrayinfo` is very local and doesn't account
759
748
for flow-sensitivity nor complex aliasing.
760
749
Ideally this dimension analysis should be done as a part of type inference that
761
750
propagates array dimensions in a flow sensitive way.
762
751
"""
763
- function compute_frameinfo (ir:: IRCode , call_resolved :: Bool )
752
+ function compute_frameinfo (ir:: IRCode )
764
753
nstmts, nnewnodes = length (ir. stmts), length (ir. new_nodes. stmts)
765
754
tryregions, arrayinfo = nothing , nothing
766
- if ! call_resolved
767
- callinfo = Vector {Any} (undef, nstmts+ nnewnodes)
768
- else
769
- callinfo = nothing
770
- end
771
755
for idx in 1 : nstmts+ nnewnodes
772
756
inst = ir[SSAValue (idx)]
773
757
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 )
782
759
@assert idx ≤ nstmts " try/catch inside new_nodes unsupported"
783
760
tryregions === nothing && (tryregions = UnitRange{Int}[])
784
761
leave_block = stmt. args[1 ]:: Int
@@ -851,14 +828,7 @@ function compute_frameinfo(ir::IRCode, call_resolved::Bool)
851
828
end
852
829
@label next_stmt
853
830
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
862
832
end
863
833
864
834
# propagate changes, and check convergence
906
876
return false
907
877
end
908
878
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
910
880
@inline function propagate_liveness_change! (estate:: EscapeState , change:: LivenessChange )
911
881
(; xidx, livepc) = change
912
882
info = estate. escapes[xidx]
@@ -1149,21 +1119,17 @@ escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}) =
1149
1119
escape_invoke! (astate, pc, args, first (args):: MethodInstance , 2 )
1150
1120
1151
1121
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)
1159
1125
if cache === nothing
1160
1126
return add_conservative_changes! (astate, pc, args, 2 )
1161
1127
else
1162
1128
cache = cache:: ArgEscapeCache
1163
1129
end
1164
1130
ret = SSAValue (pc)
1165
1131
retinfo = astate. estate[ret] # escape information imposed on the call statement
1166
- method = linfo . def:: Method
1132
+ method = mi . def:: Method
1167
1133
nargs = Int (method. nargs)
1168
1134
for (i, argidx) in enumerate (first_idx: last_idx)
1169
1135
arg = args[argidx]
@@ -1172,18 +1138,14 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any},
1172
1138
# COMBAK will this be invalid once we take alias information into account?
1173
1139
i = nargs
1174
1140
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
1182
1148
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)
1187
1149
end
1188
1150
end
1189
1151
for (; aidx, bidx) in cache. argaliases
@@ -1194,24 +1156,22 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any},
1194
1156
end
1195
1157
1196
1158
"""
1197
- from_interprocedural(arginfo ::ArgEscapeInfo, pc::Int) -> x::EscapeInfo
1159
+ from_interprocedural(argescape ::ArgEscapeInfo, pc::Int) -> x::EscapeInfo
1198
1160
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 `
1200
1162
in the context of the caller frame, where `pc` is the SSA statement number of the return value.
1201
1163
"""
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)
1215
1175
end
1216
1176
1217
1177
# escape every argument `(args[6:length(args[3])])` and the name `args[1]`
@@ -1270,27 +1230,6 @@ end
1270
1230
1271
1231
normalize (@nospecialize x) = isa (x, QuoteNode) ? x. value : x
1272
1232
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
-
1294
1233
function escape_call! (astate:: AnalysisState , pc:: Int , args:: Vector{Any} )
1295
1234
ir = astate. ir
1296
1235
ft = argextype (first (args), ir, ir. sptypes, ir. argtypes)
@@ -1331,16 +1270,17 @@ function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any})
1331
1270
end
1332
1271
end
1333
1272
1334
- escape_builtin! (@nospecialize (f), _... ) = return missing
1273
+ escape_builtin! (@nospecialize (f), _... ) = missing
1335
1274
1336
1275
# 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
1341
1281
# 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
1344
1284
1345
1285
function escape_builtin! (:: typeof (ifelse), astate:: AnalysisState , pc:: Int , args:: Vector{Any} )
1346
1286
length (args) == 4 || return false
0 commit comments