Skip to content

Commit c624d4f

Browse files
authored
optimizer: enhance SROA, form PhiNode for uninitialized field (#43208)
This commit addresses this [TODO comment](https://github.com/JuliaLang/julia/blob/3a5146f6b94e32bef5a812619068f82218b5482c/base/compiler/ssair/passes.jl#L831-L832), and allows SROA pass to form `PhiNode`s for uninitialized fields when we can prove there are safe `setfield!` calls in all dominated control flows. Now SROA can eliminate this sort of allocation: ```julia let src = code_typed((Bool,)) do cond r = Ref{Any}() if cond r[] = 42 else r[] = 32 end return r[] end |> only |> first @test !any(isnew, src.code) end ```
1 parent d515f05 commit c624d4f

File tree

2 files changed

+64
-17
lines changed

2 files changed

+64
-17
lines changed

base/compiler/ssair/passes.jl

+34-15
Original file line numberDiff line numberDiff line change
@@ -93,23 +93,50 @@ function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{I
9393
end
9494
end
9595

96+
# even when the allocation contains an uninitialized field, we try an extra effort to check
97+
# if this load at `idx` have any "safe" `setfield!` calls that define the field
98+
function has_safe_def(
99+
ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse,
100+
newidx::Int, idx::Int, inclusive::Bool = false)
101+
def = first(find_def_for_use(ir, domtree, allblocks, du, idx, inclusive))
102+
103+
# this field is supposed to be defined at the `:new` site (but it's not and thus this load will throw)
104+
def == newidx && return false
105+
106+
def 0 && return true # found a "safe" definition
107+
108+
# we may be able to replace this load with `PhiNode` if all the predecessors have "safe" definitions
109+
idxblock = block_for_inst(ir, idx)
110+
for pred in ir.cfg.blocks[idxblock].preds
111+
lastidx = last(ir.cfg.blocks[pred].stmts)
112+
# NOTE `lastidx` isn't a load, thus we can use inclusive coondition within the `find_def_for_use`
113+
has_safe_def(ir, domtree, allblocks, du, newidx, lastidx, true) || return false
114+
end
115+
return true
116+
end
117+
96118
# find the first dominating def for the given use
97-
function find_def_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, use_idx::Int)
98-
stmtblock = block_for_inst(ir.cfg, use_idx)
99-
curblock = find_curblock(domtree, allblocks, stmtblock)
119+
function find_def_for_use(
120+
ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, use::Int, inclusive::Bool=false)
121+
useblock = block_for_inst(ir.cfg, use)
122+
curblock = find_curblock(domtree, allblocks, useblock)
100123
local def = 0
101124
for idx in du.defs
102125
if block_for_inst(ir.cfg, idx) == curblock
103-
if curblock != stmtblock
126+
if curblock != useblock
104127
# Find the last def in this block
105128
def = max(def, idx)
106129
else
107130
# Find the last def before our use
108-
def = max(def, idx >= use_idx ? 0 : idx)
131+
if inclusive
132+
def = max(def, idx use ? idx : 0)
133+
else
134+
def = max(def, idx < use ? idx : 0)
135+
end
109136
end
110137
end
111138
end
112-
return def, stmtblock, curblock
139+
return def, useblock, curblock
113140
end
114141

115142
function collect_leaves(compact::IncrementalCompact, @nospecialize(val), @nospecialize(typeconstraint))
@@ -842,16 +869,8 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
842869
allblocks = sort(vcat(phiblocks, ldu.def_bbs))
843870
blocks[fidx] = phiblocks, allblocks
844871
if fidx + 1 > length(defexpr.args)
845-
# even if the allocation contains an uninitialized field, we try an extra effort
846-
# to check if all uses have any "solid" `setfield!` calls that define the field
847-
# although we give up the cases below:
848-
# `def == idx`: this field can only defined at the allocation site (thus this case will throw)
849-
# `def == 0`: this field comes from `PhiNode`
850-
# we may be able to traverse control flows of PhiNode values, but it sounds
851-
# more complicated than beneficial under the current implementation
852872
for use in du.uses
853-
def = find_def_for_use(ir, domtree, allblocks, du, use)[1]
854-
(def == 0 || def == newidx) && @goto skip
873+
has_safe_def(ir, domtree, allblocks, du, newidx, use) || @goto skip
855874
end
856875
end
857876
end

test/compiler/irpasses.jl

+30-2
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ let src = code_typed1((Bool,)) do cond
147147
end
148148
@test !any(isnew, src.code)
149149
end
150-
# FIXME to handle this case, we need a more strong alias analysis
151150
let src = code_typed1((Bool,)) do cond
152151
r = Ref{Any}()
153152
if cond
@@ -157,7 +156,22 @@ let src = code_typed1((Bool,)) do cond
157156
end
158157
return r[]
159158
end
160-
@test_broken !any(isnew, src.code)
159+
@test !any(isnew, src.code)
160+
end
161+
let src = code_typed1((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z
162+
r = Ref{Any}()
163+
if c1
164+
if c2
165+
r[] = x
166+
else
167+
r[] = y
168+
end
169+
else
170+
r[] = z
171+
end
172+
return r[]
173+
end
174+
@test !any(isnew, src.code)
161175
end
162176
let src = code_typed1((Bool,)) do cond
163177
r = Ref{Any}()
@@ -169,6 +183,20 @@ let src = code_typed1((Bool,)) do cond
169183
# N.B. `r` should be allocated since `cond` might be `false` and then it will be thrown
170184
@test any(isnew, src.code)
171185
end
186+
let src = code_typed1((Bool,Bool,Any,Any)) do c1, c2, x, y
187+
r = Ref{Any}()
188+
if c1
189+
if c2
190+
r[] = x
191+
end
192+
else
193+
r[] = y
194+
end
195+
return r[]
196+
end
197+
# N.B. `r` should be allocated since `c2` might be `false` and then it will be thrown
198+
@test any(isnew, src.code)
199+
end
172200

173201
# should include a simple alias analysis
174202
struct ImmutableOuter{T}; x::T; y::T; z::T; end

0 commit comments

Comments
 (0)