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
640
- `get_escape_cache(::Union{InferenceResult,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]
@@ -1201,17 +1167,15 @@ in the context of the caller frame, where `pc` is the SSA statement number of th
1201
1167
"""
1202
1168
function from_interprocedural (arginfo:: ArgEscapeInfo , pc:: Int )
1203
1169
has_all_escape (arginfo) && return ⊤
1204
-
1205
- ThrownEscape = has_thrown_escape (arginfo) ? LivenessSet (pc) : BOT_THROWN_ESCAPE
1206
-
1170
+ ThrownEscape = has_thrown_escape (arginfo) ? BitSet (pc) : BOT_THROWN_ESCAPE
1207
1171
return EscapeInfo (
1208
1172
#= Analyzed=# true , #= ReturnEscape=# false , ThrownEscape,
1209
1173
# FIXME implement interprocedural memory effect-analysis
1210
1174
# currently, this essentially disables the entire field analysis
1211
1175
# it might be okay from the SROA point of view, since we can't remove the allocation
1212
1176
# as far as it's passed to a callee anyway, but still we may want some field analysis
1213
1177
# for e.g. stack allocation or some other IPO optimizations
1214
- #= AliasInfo=# true , #= Liveness=# LivenessSet (pc))
1178
+ #= AliasInfo=# true , #= Liveness=# BitSet (pc))
1215
1179
end
1216
1180
1217
1181
# escape every argument `(args[6:length(args[3])])` and the name `args[1]`
@@ -1270,27 +1234,6 @@ end
1270
1234
1271
1235
normalize (@nospecialize x) = isa (x, QuoteNode) ? x. value : x
1272
1236
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
1237
function escape_call! (astate:: AnalysisState , pc:: Int , args:: Vector{Any} )
1295
1238
ir = astate. ir
1296
1239
ft = argextype (first (args), ir, ir. sptypes, ir. argtypes)
@@ -1331,16 +1274,17 @@ function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any})
1331
1274
end
1332
1275
end
1333
1276
1334
- escape_builtin! (@nospecialize (f), _... ) = return missing
1277
+ escape_builtin! (@nospecialize (f), _... ) = missing
1335
1278
1336
1279
# 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
1280
+ escape_builtin! (:: typeof (isa), _... ) = false
1281
+ escape_builtin! (:: typeof (typeof), _... ) = false
1282
+ escape_builtin! (:: typeof (sizeof), _... ) = false
1283
+ escape_builtin! (:: typeof (=== ), _... ) = false
1284
+ escape_builtin! (:: typeof (Core. donotdelete), _... ) = false
1341
1285
# not really safe, but `ThrownEscape` will be imposed later
1342
- escape_builtin! (:: typeof (isdefined), _... ) = return false
1343
- escape_builtin! (:: typeof (throw), _... ) = return false
1286
+ escape_builtin! (:: typeof (isdefined), _... ) = false
1287
+ escape_builtin! (:: typeof (throw), _... ) = false
1344
1288
1345
1289
function escape_builtin! (:: typeof (ifelse), astate:: AnalysisState , pc:: Int , args:: Vector{Any} )
1346
1290
length (args) == 4 || return false
0 commit comments