Skip to content

Commit b34cb17

Browse files
KenoaviateskIan Atol
authored
adce_pass: Drop phinode edges that can be proved unused (#43922)
* optimizer: lift more comparisons This commit implements more comparison liftings. Especially, this change enables the compiler to lift `isa`/`isdefined` checks (i.e. replace a comparison call with ϕ-node by CFG union-splitting). For example, the code snippet below will run 500x faster: ```julia function compute(n) s = 0 itr = 1:n st = iterate(itr) while isdefined(st, 2) # mimic our iteration protocol with `isdefined` v, st = st s += v st = iterate(itr, st) end s end ``` Although it seems like the codegen for `isa` is fairly optimized already and so I could not find any performance benefit for `isa`-lifting (`code_llvm` emits mostly equivalent code), but I hope it's more ideal if we can do the equivalent optimization on Julia level so that we can just consult to `code_typed` for performance optimization. * adce_pass: Drop phinode uses that can be proved unused Union splitting introduces patterns like: Consider a case like: ``` f(x::Float64) = println(x) f(x::SomeBigStruct) = nothing ``` ``` goto 2 if not ... 1: a = ::Float64 goto 3 2: b = new(SomeBigStruct, ...)::SomeBigStruct 3: c = phi(a, b) if !isa(c, Float64) goto 5 4: d = PiNode(c, Float64) println(d) goto 6 5: nothing 6: return nothing ``` Now, #43227 will turn this into: ``` goto 2 if not ... 1: a = ::Float64 goto 3 2: b = new(SomeBigStruct, ...)::SomeBigStruct 3: c = phi(a, b) cond = phi(true, false) if !cond goto 5 4: d = PiNode(c, Float64) println(d) goto 6 5: nothing 6: return nothing ``` But even though dynamically `b` is never used, it doesn't get deleted, because there's still a use in the PhiNode `c`. This PR teaches adce to recognize this situation and, for PhiNodes that are only ever used by PiNodes, drop any edges that are known to be unused. E.g. in the above case, the adce improvements would turn it into: ``` goto 2 if not ... 1: a = ::Float64 goto 3 2: b = new(SomeBigStruct, ...)::SomeBigStruct 3: c = phi(a) cond = phi(true, false) if !cond goto 5 4: d = PiNode(c, Float64) println(d) goto 6 5: nothing 6: return nothing ``` Which in turn would let regular DCE drop the allocation: ``` goto 2 if not ... 1: a = ::Float64 goto 3 2: nothing 3: c = phi(a) cond = phi(true, false) if !cond goto 5 4: d = PiNode(c, Float64) println(d) goto 6 5: nothing 6: return nothing ``` Co-authored-by: Shuhei Kadowaki <aviatesk@gmail.com> Co-authored-by: Ian Atol <ian.atol@juliacomputing.com>
1 parent 40bceb2 commit b34cb17

File tree

2 files changed

+82
-5
lines changed

2 files changed

+82
-5
lines changed

base/compiler/ssair/passes.jl

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,11 @@ function mark_phi_cycles!(compact::IncrementalCompact, safe_phis::SPCSet, phi::I
10601060
end
10611061
end
10621062

1063+
function is_union_phi(compact::IncrementalCompact, idx::Int)
1064+
inst = compact.result[idx]
1065+
return isa(inst[:inst], PhiNode) && isa(inst[:type], Union)
1066+
end
1067+
10631068
"""
10641069
adce_pass!(ir::IRCode) -> newir::IRCode
10651070
@@ -1082,22 +1087,77 @@ within `sroa_pass!` which redirects references of `typeassert`ed value to the co
10821087
function adce_pass!(ir::IRCode)
10831088
phi_uses = fill(0, length(ir.stmts) + length(ir.new_nodes))
10841089
all_phis = Int[]
1090+
unionphis = Int[] # sorted
1091+
unionphi_types = Any[]
10851092
compact = IncrementalCompact(ir)
10861093
for ((_, idx), stmt) in compact
10871094
if isa(stmt, PhiNode)
10881095
push!(all_phis, idx)
1089-
elseif is_known_call(stmt, typeassert, compact) && length(stmt.args) == 3
1090-
# nullify safe `typeassert` calls
1091-
ty, isexact = instanceof_tfunc(argextype(stmt.args[3], compact))
1092-
if isexact && argextype(stmt.args[2], compact) ty
1093-
compact[idx] = nothing
1096+
if isa(compact.result[idx][:type], Union)
1097+
push!(unionphis, idx)
1098+
push!(unionphi_types, Union{})
1099+
end
1100+
elseif isa(stmt, PiNode)
1101+
val = stmt.val
1102+
if isa(val, SSAValue) && is_union_phi(compact, val.id)
1103+
r = searchsorted(unionphis, val.id)
1104+
if !isempty(r)
1105+
unionphi_types[first(r)] = Union{unionphi_types[first(r)], widenconst(stmt.typ)}
1106+
end
1107+
end
1108+
else
1109+
if is_known_call(stmt, typeassert, compact) && length(stmt.args) == 3
1110+
# nullify safe `typeassert` calls
1111+
ty, isexact = instanceof_tfunc(argextype(stmt.args[3], compact))
1112+
if isexact && argextype(stmt.args[2], compact) ty
1113+
compact[idx] = nothing
1114+
continue
1115+
end
1116+
end
1117+
for ur in userefs(stmt)
1118+
use = ur[]
1119+
if isa(use, SSAValue) && is_union_phi(compact, use.id)
1120+
r = searchsorted(unionphis, use.id)
1121+
if !isempty(r)
1122+
deleteat!(unionphis, first(r))
1123+
deleteat!(unionphi_types, first(r))
1124+
end
1125+
end
10941126
end
10951127
end
10961128
end
10971129
non_dce_finish!(compact)
10981130
for phi in all_phis
10991131
count_uses(compact.result[phi][:inst]::PhiNode, phi_uses)
11001132
end
1133+
# Narrow any union phi nodes that have unused branches
1134+
@assert length(unionphis) == length(unionphi_types)
1135+
for i = 1:length(unionphis)
1136+
phi = unionphis[i]
1137+
t = unionphi_types[i]
1138+
if phi_uses[phi] != 0
1139+
continue
1140+
end
1141+
if t === Union{}
1142+
compact.result[phi][:inst] = nothing
1143+
continue
1144+
end
1145+
to_drop = Int[]
1146+
stmt = compact[phi]
1147+
stmt === nothing && continue
1148+
for i = 1:length(stmt.values)
1149+
if !isassigned(stmt.values, i)
1150+
# Should be impossible to have something used only by PiNodes that's undef
1151+
push!(to_drop, i)
1152+
elseif !hasintersect(widenconst(argextype(stmt.values[i], compact)), t)
1153+
push!(to_drop, i)
1154+
end
1155+
end
1156+
isempty(to_drop) && continue
1157+
deleteat!(stmt.values, to_drop)
1158+
deleteat!(stmt.edges, to_drop)
1159+
compact.result[phi][:type] = t
1160+
end
11011161
# Perform simple DCE for unused values
11021162
extra_worklist = Int[]
11031163
for (idx, nused) in Iterators.enumerate(compact.used_ssas)

test/compiler/irpasses.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,3 +838,20 @@ let ci = code_typed1(optimize=false) do
838838
ir = Core.Compiler.compact!(ir, true)
839839
@test count(@nospecialize(stmt)->isa(stmt, Core.GotoIfNot), ir.stmts.inst) == 0
840840
end
841+
842+
# Test that adce_pass! can drop phi node uses that can be concluded unused
843+
# from PiNode analysis.
844+
let src = @eval Module() begin
845+
@noinline mkfloat() = rand(Float64)
846+
@noinline use(a::Float64) = ccall(:jl_, Cvoid, (Any,), a)
847+
dispatch(a::Float64) = use(a)
848+
dispatch(a::Tuple) = nothing
849+
function foo(b)
850+
a = mkfloat()
851+
a = b ? (a, 2.0) : a
852+
dispatch(a)
853+
end
854+
code_typed(foo, Tuple{Bool})[1][1]
855+
end
856+
@test count(iscall((src, Core.tuple)), src.code) == 0
857+
end

0 commit comments

Comments
 (0)