@@ -7,15 +7,100 @@ function compact_exprtype(compact, value)
77 exprtype (value, compact. ir, compact. ir. mod)
88end
99
10- function getfield_elim_pass! (ir:: IRCode )
10+ struct SSADefUse
11+ uses:: Vector{Int}
12+ defs:: Vector{Int}
13+ end
14+ SSADefUse () = SSADefUse (Int[], Int[])
15+
16+ function try_compute_fieldidx (typ, use_expr)
17+ field = use_expr. args[3 ]
18+ isa (field, QuoteNode) && (field = field. value)
19+ isa (field, Union{Int, Symbol}) || return nothing
20+ if isa (field, Symbol)
21+ field = fieldindex (typ, field, false )
22+ field == 0 && return nothing
23+ elseif isa (field, Integer)
24+ (1 <= field <= fieldcount (typ)) || return nothing
25+ end
26+ return field
27+ end
28+
29+ function lift_defuse (cfg:: CFG , ssa:: SSADefUse )
30+ SSADefUse (
31+ Int[block_for_inst (cfg, x) for x in ssa. uses],
32+ Int[block_for_inst (cfg, x) for x in ssa. defs])
33+ end
34+
35+ function find_curblock (domtree, allblocks, curblock)
36+ # TODO : This can be much faster by looking at current level and only
37+ # searching for those blocks in a sorted order
38+ while ! (curblock in allblocks)
39+ curblock = domtree. idoms[curblock]
40+ end
41+ curblock
42+ end
43+
44+ function val_for_def_expr (ir, def, fidx)
45+ if isexpr (ir[SSAValue (def)], :new )
46+ return ir[SSAValue (def)]. args[1 + fidx]
47+ else
48+ # The use is whatever the setfield was
49+ return ir[SSAValue (def)]. args[4 ]
50+ end
51+ end
52+
53+ function compute_value_for_block (ir, domtree, allblocks, du, phinodes, fidx, curblock)
54+ curblock = find_curblock (domtree, allblocks, curblock)
55+ def = reduce (max, 0 , stmt for stmt in du. defs if block_for_inst (ir. cfg, stmt) == curblock)
56+ def == 0 ? phinodes[curblock] : val_for_def_expr (ir, def, fidx)
57+ end
58+
59+ function compute_value_for_use (ir, domtree, allblocks, du, phinodes, fidx, use_idx)
60+ # Find the first dominating def
61+ curblock = stmtblock = block_for_inst (ir. cfg, use_idx)
62+ curblock = find_curblock (domtree, allblocks, curblock)
63+ defblockdefs = [stmt for stmt in du. defs if block_for_inst (ir. cfg, stmt) == curblock]
64+ def = 0
65+ if ! isempty (defblockdefs)
66+ if curblock != stmtblock
67+ # Find the last def in this block
68+ def = maximum (defblockdefs)
69+ else
70+ # Find the last def before our use
71+ def = mapreduce (x-> x >= use_idx ? 0 : x, max, defblockdefs)
72+ end
73+ end
74+ if def == 0
75+ if ! haskey (phinodes, curblock)
76+ # If this happens, we need to search the predecessors for defs. Which
77+ # one doesn't matter - if it did, we'd have had a phinode
78+ return compute_value_for_block (ir, domtree, allblocks, du, phinodes, fidx, first (ir. cfg. blocks[stmtblock]. preds))
79+ end
80+ # The use is the phinode
81+ return phinodes[curblock]
82+ else
83+ return val_for_def_expr (ir, def, fidx)
84+ end
85+ end
86+
87+ function getfield_elim_pass! (ir:: IRCode , domtree)
1188 compact = IncrementalCompact (ir)
1289 insertions = Vector {Any} ()
90+ defuses = IdDict {Int, Tuple{IdSet{Int}, SSADefUse}} ()
1391 for (idx, stmt) in compact
14- # Step 1: Check whether the statement we're looking at is a getfield
1592 isa (stmt, Expr) || continue
16- is_known_call (stmt, getfield, ir, ir. mod) || continue
93+ is_getfield = false
94+ # Step 1: Check whether the statement we're looking at is a getfield/setfield!
95+ if is_known_call (stmt, setfield!, ir, ir. mod)
96+ is_setfield = true
97+ elseif is_known_call (stmt, getfield, ir, ir. mod)
98+ is_getfield = true
99+ else
100+ continue
101+ end
17102 isa (stmt. args[2 ], SSAValue) || continue
18- # # Normalize the field argument to getfield
103+ # # Normalize the field argument to getfield/setfield
19104 field = stmt. args[3 ]
20105 isa (field, QuoteNode) && (field = field. value)
21106 isa (field, Union{Int, Symbol}) || continue
@@ -26,8 +111,13 @@ function getfield_elim_pass!(ir::IRCode)
26111 typeconstraint = types (compact)[defidx]
27112 phi_locs = Tuple{Int, Int}[]
28113 # # Track definitions through PiNode/PhiNode
114+ found_def = false
115+ # # Track which PhiNodes, SSAValue intermediaries
116+ # # we forwarded through.
117+ intermediaries = IdSet {Int} ()
29118 while true
30119 if isa (def, PiNode)
120+ push! (intermediaries, defidx)
31121 typeconstraint = typeintersect (typeconstraint, def. typ)
32122 if isa (def. val, SSAValue)
33123 defidx = def. val. id
@@ -37,6 +127,8 @@ function getfield_elim_pass!(ir::IRCode)
37127 end
38128 continue
39129 elseif isa (def, PhiNode)
130+ # For now, we don't track setfields structs through phi nodes
131+ is_getfield || break
40132 possible_predecessors = collect (Iterators. filter (1 : length (def. edges)) do n
41133 isassigned (def. values, n) || return false
42134 value = def. values[n]
@@ -62,9 +154,22 @@ function getfield_elim_pass!(ir::IRCode)
62154 end
63155 continue
64156 end
157+ elseif isa (def, SSAValue)
158+ push! (intermediaries, defidx)
159+ defidx = def. id
160+ def = compact[def. id]
161+ continue
65162 end
163+ found_def = true
66164 break
67165 end
166+ found_def || continue
167+ if ! is_getfield
168+ mid, defuse = get! (defuses, defidx, (IdSet {Int} (), SSADefUse ()))
169+ push! (defuse. defs, idx)
170+ union! (mid, intermediaries)
171+ continue
172+ end
68173 # Step 3: Check if the definition we eventually end up at is either
69174 # a tuple(...) call or Expr(:new) and perform replacement.
70175 if isa (def, Expr) && is_known_call (def, tuple, ir, ir. mod) && isa (field, Int) && 1 <= field < length (def. args)
@@ -75,13 +180,14 @@ function getfield_elim_pass!(ir::IRCode)
75180 typ = unwrap_unionall (typ)
76181 end
77182 isa (typ, DataType) || continue
78- ! typ. mutable || continue
79- if isa (field, Symbol)
80- field = fieldindex (typ, field, false )
81- field == 0 && continue
82- elseif isa (field, Integer)
83- (1 <= field <= fieldcount (typ)) || continue
183+ if typ. mutable
184+ mid, defuse = get! (defuses, defidx, (IdSet {Int} (), SSADefUse ()))
185+ push! (defuse. uses, idx)
186+ union! (mid, intermediaries)
187+ continue
84188 end
189+ field = try_compute_fieldidx (typ, stmt)
190+ field === nothing && continue
85191 forwarded = def. args[1 + field]
86192 else
87193 continue
@@ -95,6 +201,76 @@ function getfield_elim_pass!(ir::IRCode)
95201 compact[idx] = forwarded
96202 end
97203 ir = finish (compact)
204+ @Base . show length (defuses)
205+ # Now go through any mutable structs and see which ones we can eliminate
206+ for (idx, (intermediaries, defuse)) in defuses
207+ intermediaries = collect (intermediaries)
208+ # Check if there are any uses we did not account for. If so, the variable
209+ # escapes and we cannot eliminate the allocation. This works, because we're guaranteed
210+ # not to include any intermediaries that have dead uses. As a result, missing uses will only ever
211+ # show up in the nuses_total count.
212+ nleaves = length (defuse. uses) + length (defuse. defs)
213+ nuses_total = compact. used_ssas[idx] + mapreduce (idx-> compact. used_ssas[idx], + , 0 , intermediaries) - length (intermediaries)
214+ @Base . show (nleaves, nuses_total)
215+ nleaves == nuses_total || continue
216+ # Find the type for this allocation
217+ defexpr = ir[SSAValue (idx)]
218+ isexpr (defexpr, :new ) || continue
219+ typ = defexpr. typ
220+ if isa (typ, UnionAll)
221+ typ = unwrap_unionall (typ)
222+ end
223+ # Could still end up here if we tried to setfield! and immutable, which would
224+ # error at runtime, but is not illegal to have in the IR.
225+ typ. mutable || continue
226+ # Partition defuses by field
227+ fielddefuse = SSADefUse[SSADefUse () for _ = 1 : fieldcount (typ)]
228+ ok = true
229+ for use in defuse. uses
230+ field = try_compute_fieldidx (typ, ir[SSAValue (use)])
231+ field === nothing && (ok = false ; break )
232+ push! (fielddefuse[field]. uses, use)
233+ end
234+ ok || continue
235+ for use in defuse. defs
236+ field = try_compute_fieldidx (typ, ir[SSAValue (use)])
237+ field === nothing && (ok = false ; break )
238+ push! (fielddefuse[field]. defs, use)
239+ end
240+ ok || continue
241+ # Everything accounted for. Go field by field and perform idf
242+ for (fidx, du) in pairs (fielddefuse)
243+ ftyp = fieldtype (typ, fidx)
244+ if ! isempty (du. uses)
245+ push! (du. defs, idx)
246+ ldu = lift_defuse (ir. cfg, du)
247+ phiblocks = idf (ir. cfg, ldu, domtree)
248+ phinodes = IdDict {Int, SSAValue} ()
249+ for b in phiblocks
250+ n = PhiNode ()
251+ phinodes[b] = insert_node! (ir, first (ir. cfg. blocks[b]. stmts), ftyp, n)
252+ end
253+ # Now go through all uses and rewrite them
254+ allblocks = sort (vcat (phiblocks, ldu. defs))
255+ for stmt in du. uses
256+ ir[SSAValue (stmt)] = compute_value_for_use (ir, domtree, allblocks, du, phinodes, fidx, stmt)
257+ end
258+ for b in phiblocks
259+ for p in ir. cfg. blocks[b]. preds
260+ n = ir[phinodes[b]]
261+ push! (n. edges, p)
262+ push! (n. values, compute_value_for_block (ir, domtree,
263+ allblocks, du, phinodes, fidx, p))
264+ end
265+ end
266+ end
267+ for stmt in du. defs
268+ stmt == idx && continue
269+ ir[SSAValue (stmt)] = nothing
270+ end
271+ continue
272+ end
273+ end
98274 for (idx, phi_locs) in insertions
99275 # For non-dominating load-store forward, we may have to insert extra phi nodes
100276 # TODO : Can use the domtree to eliminate unnecessary phis, but ok for now
0 commit comments