@@ -100,9 +100,9 @@ function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{I
100
100
end
101
101
end
102
102
103
- # even when the allocation contains an uninitialized field, we try an extra effort to check
104
- # if this load at `idx` have any "safe" `setfield!` calls that define the field
105
- function has_safe_def (
103
+ # even when the allocation contains an uninitialized field, we try an extra effort to
104
+ # check if all loads have "safe" `setfield!` calls that define the uninitialized field
105
+ function has_safe_def_for_undef_field (
106
106
ir:: IRCode , domtree:: DomTree , allblocks:: Vector{Int} , du:: SSADefUse ,
107
107
newidx:: Int , idx:: Int )
108
108
def, _, _ = find_def_for_use (ir, domtree, allblocks, du, idx)
@@ -207,14 +207,15 @@ function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSA
207
207
end
208
208
209
209
function simple_walk_constraint (compact:: IncrementalCompact , @nospecialize (defssa#= ::AnySSAValue=# ),
210
- @nospecialize (typeconstraint))
211
- callback = function (@nospecialize (pi ), @nospecialize (idx))
212
- if isa (pi , PiNode)
213
- typeconstraint = typeintersect (typeconstraint, widenconst (pi . typ))
210
+ @nospecialize (typeconstraint), @nospecialize (callback = nothing ) )
211
+ newcallback = function (@nospecialize (x ), @nospecialize (idx))
212
+ if isa (x , PiNode)
213
+ typeconstraint = typeintersect (typeconstraint, widenconst (x . typ))
214
214
end
215
+ callback === nothing || callback (x, idx)
215
216
return false
216
217
end
217
- def = simple_walk (compact, defssa, callback )
218
+ def = simple_walk (compact, defssa, newcallback )
218
219
return Pair {Any, Any} (def, typeconstraint)
219
220
end
220
221
224
225
Starting at `val` walk use-def chains to get all the leaves feeding into this `val`
225
226
(pruning those leaves rules out by path conditions).
226
227
"""
227
- function walk_to_defs (compact:: IncrementalCompact , @nospecialize (defssa), @nospecialize (typeconstraint))
228
+ function walk_to_defs (compact:: IncrementalCompact ,
229
+ @nospecialize (defssa), @nospecialize (typeconstraint),
230
+ @nospecialize (callback = nothing ))
228
231
visited_phinodes = AnySSAValue[]
229
232
isa (defssa, AnySSAValue) || return Any[defssa], visited_phinodes
230
233
def = compact[defssa]
@@ -260,7 +263,7 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe
260
263
val = OldSSAValue (val. id)
261
264
end
262
265
if isa (val, AnySSAValue)
263
- new_def, new_constraint = simple_walk_constraint (compact, val, typeconstraint)
266
+ new_def, new_constraint = simple_walk_constraint (compact, val, typeconstraint, callback )
264
267
if isa (new_def, AnySSAValue)
265
268
if ! haskey (visited_constraints, new_def)
266
269
push! (worklist_defs, new_def)
@@ -721,10 +724,10 @@ function sroa_pass!(ir::IRCode, optional_opts::Bool = true)
721
724
continue
722
725
end
723
726
if defuses === nothing
724
- defuses = IdDict {Int, Tuple{SPCSet, SSADefUse}} ()
727
+ defuses = IdDict {Int, Tuple{SPCSet, SSADefUse, PhiDefs }} ()
725
728
end
726
- mid, defuse = get! (defuses, defidx) do
727
- SPCSet (), SSADefUse ()
729
+ mid, defuse, phidefs = get! (defuses, defidx) do
730
+ SPCSet (), SSADefUse (), PhiDefs ( nothing )
728
731
end
729
732
push! (defuse. ccall_preserve_uses, idx)
730
733
union! (mid, intermediaries)
@@ -779,16 +782,29 @@ function sroa_pass!(ir::IRCode, optional_opts::Bool = true)
779
782
# Mutable stuff here
780
783
isa (def, SSAValue) || continue
781
784
if defuses === nothing
782
- defuses = IdDict {Int, Tuple{SPCSet, SSADefUse}} ()
785
+ defuses = IdDict {Int, Tuple{SPCSet, SSADefUse, PhiDefs }} ()
783
786
end
784
- mid, defuse = get! (defuses, def. id) do
785
- SPCSet (), SSADefUse ()
787
+ mid, defuse, phidefs = get! (defuses, def. id) do
788
+ SPCSet (), SSADefUse (), PhiDefs ( nothing )
786
789
end
787
790
if is_setfield
788
791
push! (defuse. defs, idx)
789
792
else
790
793
push! (defuse. uses, idx)
791
794
end
795
+ defval = compact[def]
796
+ if isa (defval, PhiNode)
797
+ phicallback = function (@nospecialize (x), @nospecialize (ssa))
798
+ push! (intermediaries, ssa. id)
799
+ return false
800
+ end
801
+ defs, _ = walk_to_defs (compact, def, struct_typ, phicallback)
802
+ if _any (@nospecialize (d)-> ! isa (d, SSAValue), defs)
803
+ delete! (defuses, def. id)
804
+ continue
805
+ end
806
+ phidefs[] = Int[(def:: SSAValue ). id for def in defs]
807
+ end
792
808
union! (mid, intermediaries)
793
809
end
794
810
continue
@@ -848,43 +864,73 @@ function sroa_pass!(ir::IRCode, optional_opts::Bool = true)
848
864
end
849
865
end
850
866
867
+ # TODO :
868
+ # - run mutable SROA on the same IR as when we collect information about mutable allocations
869
+ # - simplify and improve the eliminability check below using an escape analysis
870
+
871
+ const PhiDefs = RefValue{Union{Nothing,Vector{Int}}}
872
+
851
873
function sroa_mutables! (ir:: IRCode ,
852
- defuses:: IdDict{Int, Tuple{SPCSet, SSADefUse}} , used_ssas:: Vector{Int} ,
874
+ defuses:: IdDict{Int, Tuple{SPCSet, SSADefUse, PhiDefs }} , used_ssas:: Vector{Int} ,
853
875
nested_loads:: NestedLoads )
854
876
domtree = nothing # initialization of domtree is delayed to avoid the expensive computation in many cases
855
877
nested_mloads = NestedLoads () # tracks nested `getfield(getfield(...)::Mutable, ...)::Mutable`
856
878
any_eliminated = false
879
+ eliminable_defs = nothing # tracks eliminable "definitions" if initialized
857
880
# NOTE eliminate from innermost definitions, so that we can track elimination of nested `getfield`
858
- for (idx, (intermediaries, defuse)) in sort! (collect (defuses); by= first, rev= true )
881
+ for (idx, (intermediaries, defuse, phidefs )) in sort! (collect (defuses); by= first, rev= true )
859
882
intermediaries = collect (intermediaries)
883
+ phidefs = phidefs[]
860
884
# Check if there are any uses we did not account for. If so, the variable
861
885
# escapes and we cannot eliminate the allocation. This works, because we're guaranteed
862
886
# not to include any intermediaries that have dead uses. As a result, missing uses will only ever
863
887
# show up in the nuses_total count.
864
- nleaves = length (defuse. uses) + length (defuse. defs) + length (defuse. ccall_preserve_uses)
888
+ nleaves = count_leaves (defuse)
889
+ if phidefs != = nothing
890
+ # if this defines ϕ, we also track leaves of all predecessors as well
891
+ # FIXME this doesn't work when any predecessor is used by another ϕ-node
892
+ for pidx in phidefs
893
+ haskey (defuses, pidx) || continue
894
+ pdefuse = defuses[pidx][2 ]
895
+ nleaves += count_leaves (pdefuse)
896
+ end
897
+ end
865
898
nuses = 0
866
899
for idx in intermediaries
867
900
nuses += used_ssas[idx]
868
901
end
869
- nuses_total = used_ssas[idx] + nuses - length (intermediaries)
902
+ nuses -= length (intermediaries)
903
+ nuses_total = used_ssas[idx] + nuses
904
+ if phidefs != = nothing
905
+ for pidx in phidefs
906
+ # NOTE we don't need to accout for intermediates for this predecessor here,
907
+ # since they are already included in intermediates of this ϕ-node
908
+ # FIXME this doesn't work when any predecessor is used by another ϕ-node
909
+ nuses_total += used_ssas[pidx] - 1 # substract usage count from ϕ-node itself
910
+ end
911
+ end
870
912
nleaves == nuses_total || continue
871
913
# Find the type for this allocation
872
914
defexpr = ir[SSAValue (idx)]
873
- isa (defexpr, Expr) || continue
874
- if ! isexpr (defexpr, :new )
875
- if is_known_call (defexpr, getfield, ir)
876
- val = defexpr. args[2 ]
877
- if isa (val, SSAValue)
878
- struct_typ = unwrap_unionall (widenconst (argextype (val, ir)))
879
- if ismutabletype (struct_typ)
880
- record_nested_load! (nested_mloads, idx)
881
- end
915
+ if isa (defexpr, Expr)
916
+ @assert phidefs === nothing
917
+ if ! isexpr (defexpr, :new )
918
+ maybe_record_nested_load! (nested_mloads, ir, idx)
919
+ continue
920
+ end
921
+ elseif isa (defexpr, PhiNode)
922
+ phidefs === nothing && continue
923
+ for pidx in phidefs
924
+ pexpr = ir[SSAValue (pidx)]
925
+ if ! isexpr (pexpr, :new )
926
+ maybe_record_nested_load! (nested_mloads, ir, pidx)
927
+ @goto skip
882
928
end
883
929
end
930
+ else
884
931
continue
885
932
end
886
- newidx = idx
887
- typ = ir. stmts[newidx][:type ]
933
+ typ = ir. stmts[idx][:type ]
888
934
if isa (typ, UnionAll)
889
935
typ = unwrap_unionall (typ)
890
936
end
@@ -896,25 +942,29 @@ function sroa_mutables!(ir::IRCode,
896
942
fielddefuse = SSADefUse[SSADefUse () for _ = 1 : fieldcount (typ)]
897
943
all_forwarded = true
898
944
for use in defuse. uses
899
- stmt = ir[ SSAValue (use)] # == `getfield` call
900
- # We may have discovered above that this use is dead
901
- # after the getfield elim of immutables. In that case,
902
- # it would have been deleted. That's fine, just ignore
903
- # the use in that case.
904
- if stmt === nothing
945
+ eliminable = check_use_eliminability! (fielddefuse, ir, use, typ)
946
+ if eliminable === nothing
947
+ # We may have discovered above that this use is dead
948
+ # after the getfield elim of immutables. In that case,
949
+ # it would have been deleted. That's fine, just ignore
950
+ # the use in that case.
905
951
all_forwarded = false
906
952
continue
953
+ elseif ! eliminable
954
+ @goto skip
907
955
end
908
- field = try_compute_fieldidx_stmt (ir, stmt:: Expr , typ)
909
- field === nothing && @goto skip
910
- push! (fielddefuse[field]. uses, use)
911
956
end
912
957
for def in defuse. defs
913
- stmt = ir[SSAValue (def)]:: Expr # == `setfield!` call
914
- field = try_compute_fieldidx_stmt (ir, stmt, typ)
915
- field === nothing && @goto skip
916
- isconst (typ, field) && @goto skip # we discovered an attempt to mutate a const field, which must error
917
- push! (fielddefuse[field]. defs, def)
958
+ check_def_eliminability! (fielddefuse, ir, def, typ) || @goto skip
959
+ end
960
+ if phidefs != = nothing
961
+ for pidx in phidefs
962
+ haskey (defuses, pidx) || continue
963
+ pdefuse = defuses[pidx][2 ]
964
+ for pdef in pdefuse. defs
965
+ check_def_eliminability! (fielddefuse, ir, pdef, typ) || @goto skip
966
+ end
967
+ end
918
968
end
919
969
# Check that the defexpr has defined values for all the fields
920
970
# we're accessing. In the future, we may want to relax this,
@@ -925,7 +975,13 @@ function sroa_mutables!(ir::IRCode,
925
975
for fidx in 1 : ndefuse
926
976
du = fielddefuse[fidx]
927
977
isempty (du. uses) && continue
928
- push! (du. defs, newidx)
978
+ if phidefs === nothing
979
+ push! (du. defs, idx)
980
+ else
981
+ for pidx in phidefs
982
+ push! (du. defs, pidx)
983
+ end
984
+ end
929
985
ldu = compute_live_ins (ir. cfg, du)
930
986
if isempty (ldu. live_in_bbs)
931
987
phiblocks = Int[]
@@ -935,10 +991,24 @@ function sroa_mutables!(ir::IRCode,
935
991
end
936
992
allblocks = sort (vcat (phiblocks, ldu. def_bbs))
937
993
blocks[fidx] = phiblocks, allblocks
938
- if fidx + 1 > length (defexpr. args)
939
- for use in du. uses
994
+ if phidefs != = nothing
995
+ # check if all predecessors have safe definitions
996
+ for pidx in phidefs
997
+ newexpr = ir[SSAValue (pidx)]:: Expr # == new(...)
998
+ if fidx + 1 > length (newexpr. args) # this field can be undefined
999
+ domtree === nothing && (@timeit " domtree 2" domtree = construct_domtree (ir. cfg. blocks))
1000
+ for use in du. uses
1001
+ has_safe_def_for_undef_field (ir, domtree, allblocks, du, pidx, use) || @goto skip
1002
+ end
1003
+ end
1004
+ end
1005
+ else
1006
+ newexpr = defexpr:: Expr # == new(...)
1007
+ if fidx + 1 > length (newexpr. args) # this field can be undefined
940
1008
domtree === nothing && (@timeit " domtree 2" domtree = construct_domtree (ir. cfg. blocks))
941
- has_safe_def (ir, domtree, allblocks, du, newidx, use) || @goto skip
1009
+ for use in du. uses
1010
+ has_safe_def_for_undef_field (ir, domtree, allblocks, du, idx, use) || @goto skip
1011
+ end
942
1012
end
943
1013
end
944
1014
end
@@ -983,28 +1053,79 @@ function sroa_mutables!(ir::IRCode,
983
1053
end
984
1054
end
985
1055
end
986
- for stmt in du. defs
987
- stmt == newidx && continue
988
- ir[SSAValue (stmt)] = nothing
1056
+ eliminable_defs === nothing && (eliminable_defs = SPCSet ())
1057
+ for def in du. defs
1058
+ push! (eliminable_defs, def)
1059
+ end
1060
+ if phidefs != = nothing
1061
+ # record ϕ-node itself eliminable here, since we didn't include it in `du.defs`
1062
+ # we also modify usage counts of its predecessors so that their SROA may work
1063
+ # in succeeding iteration
1064
+ push! (eliminable_defs, idx)
1065
+ for pidx in phidefs
1066
+ used_ssas[pidx] -= 1
1067
+ end
989
1068
end
990
1069
end
991
1070
preserve_uses === nothing && continue
992
1071
if all_forwarded
993
1072
# this means all ccall preserves have been replaced with forwarded loads
994
1073
# so we can potentially eliminate the allocation, otherwise we must preserve
995
1074
# the whole allocation.
996
- push! (intermediaries, newidx )
1075
+ push! (intermediaries, idx )
997
1076
end
998
1077
# Insert the new preserves
999
1078
for (use, new_preserves) in preserve_uses
1000
1079
ir[SSAValue (use)] = form_new_preserves (ir[SSAValue (use)]:: Expr , intermediaries, new_preserves)
1001
1080
end
1002
-
1003
1081
@label skip
1004
1082
end
1083
+ # now eliminate "definitions" (i.e. allocations, ϕ-nodes, and `setfield!` calls)
1084
+ # that should have no usage at this moment
1085
+ if eliminable_defs != = nothing
1086
+ for idx in eliminable_defs
1087
+ ir[SSAValue (idx)] = nothing
1088
+ end
1089
+ end
1005
1090
return any_eliminated ? sroa_pass! (compact! (ir), false ) : ir
1006
1091
end
1007
1092
1093
+ count_leaves (defuse:: SSADefUse ) =
1094
+ length (defuse. uses) + length (defuse. defs) + length (defuse. ccall_preserve_uses)
1095
+
1096
+ function maybe_record_nested_load! (nested_mloads:: NestedLoads , ir:: IRCode , idx:: Int )
1097
+ defexpr = ir[SSAValue (idx)]
1098
+ if is_known_call (defexpr, getfield, ir)
1099
+ val = defexpr. args[2 ]
1100
+ if isa (val, SSAValue)
1101
+ struct_typ = unwrap_unionall (widenconst (argextype (val, ir)))
1102
+ if ismutabletype (struct_typ)
1103
+ record_nested_load! (nested_mloads, idx)
1104
+ end
1105
+ end
1106
+ end
1107
+ end
1108
+
1109
+ function check_use_eliminability! (fielddefuse:: Vector{SSADefUse} ,
1110
+ ir:: IRCode , useidx:: Int , struct_typ:: DataType )
1111
+ stmt = ir[SSAValue (useidx)] # == `getfield` call
1112
+ stmt === nothing && return nothing
1113
+ field = try_compute_fieldidx_stmt (ir, stmt:: Expr , struct_typ)
1114
+ field === nothing && return false
1115
+ push! (fielddefuse[field]. uses, useidx)
1116
+ return true
1117
+ end
1118
+
1119
+ function check_def_eliminability! (fielddefuse:: Vector{SSADefUse} ,
1120
+ ir:: IRCode , defidx:: Int , struct_typ:: DataType )
1121
+ stmt = ir[SSAValue (defidx)]:: Expr # == `setfield!` call
1122
+ field = try_compute_fieldidx_stmt (ir, stmt, struct_typ)
1123
+ field === nothing && return false
1124
+ isconst (struct_typ, field) && return false # we discovered an attempt to mutate a const field, which must error
1125
+ push! (fielddefuse[field]. defs, defidx)
1126
+ return true
1127
+ end
1128
+
1008
1129
function form_new_preserves (origex:: Expr , intermediates:: Vector{Int} , new_preserves:: Vector{Any} )
1009
1130
newex = Expr (:foreigncall )
1010
1131
nccallargs = length (origex. args[3 ]:: SimpleVector )
0 commit comments