Skip to content

Commit 1f7dda1

Browse files
committed
optimizer: switch to more general data structure of SSADefUse
Now `(du::SSADefUse).uses` field can contain arbitrary "usages" to be eliminated. This structure might be helpful for implementing array SROA, for example. Also slightly tweaks the implementation of `ccall` preserve elimination.
1 parent e205e50 commit 1f7dda1

File tree

1 file changed

+89
-37
lines changed

1 file changed

+89
-37
lines changed

base/compiler/ssair/passes.jl

+89-37
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,51 @@ end
1010
du::SSADefUse
1111
1212
This struct keeps track of all uses of some mutable struct allocated in the current function:
13-
- `du.uses::Vector{Int}` are all instances of `getfield` on the struct
13+
- `du.uses::Vector{Any}` are some "usages" (like `getfield`) of the struct
1414
- `du.defs::Vector{Int}` are all instances of `setfield!` on the struct
1515
The terminology refers to the uses/defs of the "slot bundle" that the mutable struct represents.
1616
17-
In addition we keep track of all instances of a `:foreigncall` that preserves of this mutable
18-
struct in `du.ccall_preserve_uses`. Somewhat counterintuitively, we don't actually need to
19-
make sure that the struct itself is live (or even allocated) at a `ccall` site.
20-
If there are no other places where the struct escapes (and thus e.g. where its address is taken),
21-
it need not be allocated. We do however, need to make sure to preserve any elements of this struct.
17+
`du.uses` tracks all instances of `getfield` and `isdefined` calls on the struct.
18+
Additionally it also tracks all instances of a `:foreigncall` that preserves of this mutable
19+
struct. Somewhat counterintuitively, we don't actually need to make sure that the struct
20+
itself is live (or even allocated) at a `ccall` site. If there are no other places where
21+
the struct escapes (and thus e.g. where its address is taken), it need not be allocated.
22+
We do however, need to make sure to preserve any elements of this struct.
2223
"""
2324
struct SSADefUse
24-
uses::Vector{Int}
25+
uses::Vector{Any}
2526
defs::Vector{Int}
26-
ccall_preserve_uses::Vector{Int}
2727
end
28-
SSADefUse() = SSADefUse(Int[], Int[], Int[])
28+
SSADefUse() = SSADefUse(Any[], Int[])
29+
30+
struct GetfieldUse
31+
idx::Int
32+
end
33+
struct PreserveUse
34+
idx::Int
35+
end
36+
struct NoPreserve end
37+
struct IsdefinedUse
38+
idx::Int
39+
end
40+
function getuseidx(@nospecialize use)
41+
if isa(use, GetfieldUse)
42+
return use.idx
43+
elseif isa(use, PreserveUse)
44+
return use.idx
45+
elseif isa(use, IsdefinedUse)
46+
return use.idx
47+
else
48+
@assert false "getuseidx: unexpected use"
49+
end
50+
end
2951

3052
function compute_live_ins(cfg::CFG, du::SSADefUse)
31-
uses = filter(>(0), du.uses)
53+
uses = Int[]
54+
for use in du.uses
55+
isa(use, IsdefinedUse) && continue
56+
push!(uses, getuseidx(use))
57+
end
3258
compute_live_ins(cfg, du.defs, uses)
3359
end
3460

@@ -89,7 +115,8 @@ function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::Vector
89115
def == 0 ? phinodes[curblock] : val_for_def_expr(ir, def, fidx)
90116
end
91117

92-
function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, use::Int)
118+
function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int},
119+
du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, use::Int)
93120
def, useblock, curblock = find_def_for_use(ir, domtree, allblocks, du, use)
94121
if def == 0
95122
if !haskey(phinodes, curblock)
@@ -787,8 +814,8 @@ function sroa_pass!(ir::IRCode)
787814
if defuses === nothing
788815
defuses = IdDict{Int, Tuple{SPCSet, SSADefUse}}()
789816
end
790-
mid, defuse = get!(defuses, defidx, (SPCSet(), SSADefUse()))
791-
push!(defuse.ccall_preserve_uses, idx)
817+
mid, defuse = get!(()->(SPCSet(),SSADefUse()), defuses, defidx)
818+
push!(defuse.uses, PreserveUse(idx))
792819
union!(mid, intermediaries)
793820
end
794821
continue
@@ -846,13 +873,13 @@ function sroa_pass!(ir::IRCode)
846873
if defuses === nothing
847874
defuses = IdDict{Int, Tuple{SPCSet, SSADefUse}}()
848875
end
849-
mid, defuse = get!(defuses, def.id, (SPCSet(), SSADefUse()))
876+
mid, defuse = get!(()->(SPCSet(),SSADefUse()), defuses, def.id)
850877
if is_setfield
851878
push!(defuse.defs, idx)
852879
elseif is_isdefined
853-
push!(defuse.uses, -idx)
880+
push!(defuse.uses, IsdefinedUse(idx))
854881
else
855-
push!(defuse.uses, idx)
882+
push!(defuse.uses, GetfieldUse(idx))
856883
end
857884
union!(mid, intermediaries)
858885
end
@@ -923,7 +950,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
923950
# escapes and we cannot eliminate the allocation. This works, because we're guaranteed
924951
# not to include any intermediaries that have dead uses. As a result, missing uses will only ever
925952
# show up in the nuses_total count.
926-
nleaves = length(defuse.uses) + length(defuse.defs) + length(defuse.ccall_preserve_uses)
953+
nleaves = length(defuse.uses) + length(defuse.defs)
927954
nuses = 0
928955
for idx in intermediaries
929956
nuses += used_ssas[idx]
@@ -946,7 +973,14 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
946973
fielddefuse = SSADefUse[SSADefUse() for _ = 1:fieldcount(typ)]
947974
all_eliminated = all_forwarded = true
948975
for use in defuse.uses
949-
stmt = ir[SSAValue(abs(use))][:inst] # == `getfield`/`isdefined` call
976+
if isa(use, PreserveUse)
977+
for fdu in fielddefuse
978+
push!(fdu.uses, use)
979+
end
980+
continue
981+
end
982+
useidx = getuseidx(use)
983+
stmt = ir[SSAValue(useidx)][:inst] # == `getfield`/`isdefined` call
950984
# We may have discovered above that this use is dead
951985
# after the getfield elim of immutables. In that case,
952986
# it would have been deleted. That's fine, just ignore
@@ -985,16 +1019,25 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
9851019
allblocks = sort(vcat(phiblocks, ldu.def_bbs))
9861020
blocks[fidx] = phiblocks, allblocks
9871021
if fidx + 1 > length(defexpr.args)
988-
for use in du.uses
989-
if use > 0 # == `getfield` use
990-
has_safe_def(ir, get_domtree(), allblocks, du, newidx, use) || @goto skip
991-
else # == `isdefined` use
992-
if has_safe_def(ir, get_domtree(), allblocks, du, newidx, -use)
993-
ir[SSAValue(-use)][:inst] = true
1022+
for i = 1:length(du.uses)
1023+
use = du.uses[i]
1024+
if isa(use, IsdefinedUse)
1025+
useidx = getuseidx(use)
1026+
if has_safe_def(ir, get_domtree(), allblocks, du, newidx, useidx)
1027+
ir[SSAValue(useidx)][:inst] = true
9941028
else
9951029
all_eliminated = false
9961030
end
1031+
continue
1032+
elseif isa(use, PreserveUse)
1033+
if length(du.defs) == 1 # allocation with this field unintialized
1034+
# there is nothing to preserve, just ignore this use
1035+
du.uses[i] = NoPreserve()
1036+
continue
1037+
end
9971038
end
1039+
useidx = getuseidx(use)
1040+
has_safe_def(ir, get_domtree(), allblocks, du, newidx, useidx) || @goto skip
9981041
end
9991042
end
10001043
end
@@ -1003,8 +1046,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
10031046
# This needs to be after we iterate through the IR with `IncrementalCompact`
10041047
# because removing dead blocks can invalidate the domtree.
10051048
domtree = get_domtree()
1006-
preserve_uses = isempty(defuse.ccall_preserve_uses) ? nothing :
1007-
IdDict{Int, Vector{Any}}((idx=>Any[] for idx in SPCSet(defuse.ccall_preserve_uses)))
1049+
local preserve_uses = nothing
10081050
for fidx in 1:ndefuse
10091051
du = fielddefuse[fidx]
10101052
ftyp = fieldtype(typ, fidx)
@@ -1017,17 +1059,26 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
10171059
end
10181060
# Now go through all uses and rewrite them
10191061
for use in du.uses
1020-
if use > 0 # == `getfield` use
1021-
ir[SSAValue(use)][:inst] = compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, use)
1022-
else # == `isdefined` use
1062+
if isa(use, GetfieldUse)
1063+
useidx = getuseidx(use)
1064+
ir[SSAValue(useidx)][:inst] = compute_value_for_use(ir, domtree, allblocks,
1065+
du, phinodes, fidx, useidx)
1066+
elseif isa(use, IsdefinedUse)
10231067
continue # already rewritten if possible
1024-
end
1025-
end
1026-
if !isbitstype(ftyp)
1027-
if preserve_uses !== nothing
1028-
for (use, list) in preserve_uses
1029-
push!(list, compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, use))
1068+
elseif isa(use, NoPreserve)
1069+
# nothing to preserve (may happen when there are unintialized fields)
1070+
elseif isa(use, PreserveUse)
1071+
useidx = getuseidx(use)
1072+
newval = compute_value_for_use(
1073+
ir, domtree, allblocks, du, phinodes, fidx, useidx)
1074+
if !isbitstype(widenconst(argextype(newval, ir)))
1075+
if preserve_uses === nothing
1076+
preserve_uses = IdDict{Int, Vector{Any}}()
1077+
end
1078+
push!(get!(()->Any[], preserve_uses, useidx), newval)
10301079
end
1080+
else
1081+
@assert false "sroa_mutables!: unexpected use"
10311082
end
10321083
end
10331084
for b in phiblocks
@@ -1054,8 +1105,9 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
10541105
push!(intermediaries, newidx)
10551106
end
10561107
# Insert the new preserves
1057-
for (use, new_preserves) in preserve_uses
1058-
ir[SSAValue(use)][:inst] = form_new_preserves(ir[SSAValue(use)][:inst]::Expr, intermediaries, new_preserves)
1108+
for (useidx, new_preserves) in preserve_uses
1109+
ir[SSAValue(useidx)][:inst] = form_new_preserves(ir[SSAValue(useidx)][:inst]::Expr,
1110+
intermediaries, new_preserves)
10591111
end
10601112

10611113
@label skip

0 commit comments

Comments
 (0)