Skip to content

Commit

Permalink
optimizer: keep statements that may throw from being optimized by SROA
Browse files Browse the repository at this point in the history
SROA should verify a statement won't throw, otherwise it can't be
eliminated safely.
Note that this commit skips the nothrow-ness verification on `getfield`
statement. This is acceptable because currently we are unable to prove
it in the presence of potentially undefined fields. This is okay because
our SROA pass will eliminate such a `getfield` statement only if it
determines that the forwarded value safely defines the potentially
undefined field.
  • Loading branch information
aviatesk committed Jan 3, 2023
1 parent 79e29e3 commit cab9fe9
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 19 deletions.
35 changes: 16 additions & 19 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ function compute_live_ins(cfg::CFG, du::SSADefUse)
compute_live_ins(cfg, sort!(du.defs), uses)
end

# assume `stmt == getfield(obj, field, ...)` or `stmt == setfield!(obj, field, val, ...)`
try_compute_field_stmt(ir::Union{IncrementalCompact,IRCode}, stmt::Expr) =
try_compute_field(ir, stmt.args[3])

function try_compute_field(ir::Union{IncrementalCompact,IRCode}, @nospecialize(field))
# fields are usually literals, handle them manually
if isa(field, QuoteNode)
Expand All @@ -67,8 +63,9 @@ function try_compute_field(ir::Union{IncrementalCompact,IRCode}, @nospecialize(f
return isa(field, Union{Int, Symbol}) ? field : nothing
end

# assume `stmt` is a call of `getfield`/`setfield!`/`isdefined`
function try_compute_fieldidx_stmt(ir::Union{IncrementalCompact,IRCode}, stmt::Expr, typ::DataType)
field = try_compute_field_stmt(ir, stmt)
field = try_compute_field(ir, stmt.args[3])
return try_compute_fieldidx(typ, field)
end

Expand Down Expand Up @@ -953,14 +950,11 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing)
continue
end

# analyze this `getfield` / `isdefined` / `setfield!` call

if !is_finalizer
field = try_compute_field_stmt(compact, stmt)
field === nothing && continue
val = stmt.args[2]
else
if is_finalizer
val = stmt.args[3]
else
# analyze `getfield` / `isdefined` / `setfield!` call
val = stmt.args[2]
end

struct_typ = unwrap_unionall(widenconst(argextype(val, compact)))
Expand Down Expand Up @@ -1014,7 +1008,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing)

# perform SROA on immutable structs here on

field = try_compute_fieldidx(struct_typ, field)
field = try_compute_fieldidx_stmt(compact, stmt, struct_typ)
field === nothing && continue

leaves, visited_phinodes = collect_leaves(compact, val, struct_typ, 𝕃ₒ)
Expand Down Expand Up @@ -1116,7 +1110,7 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int,
return true
end

is_nothrow(ir::IRCode, pc::Int) = (ir.stmts[pc][:flag] & IR_FLAG_NOTHROW) 0
is_nothrow(ir::IRCode, ssa::SSAValue) = (ir[ssa][:flag] & IR_FLAG_NOTHROW) 0

function reachable_blocks(cfg::CFG, from_bb::Int, to_bb::Union{Nothing,Int} = nothing)
worklist = Int[from_bb]
Expand Down Expand Up @@ -1210,7 +1204,7 @@ function try_resolve_finalizer!(ir::IRCode, idx::Int, finalizer_idx::Int, defuse
return all(s:e) do sidx::Int
sidx == finalizer_idx && return true
sidx == idx && return true
return is_nothrow(ir, sidx)
return is_nothrow(ir, SSAValue(sidx))
end
end
for bb in blocks
Expand Down Expand Up @@ -1418,11 +1412,14 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
end
all_eliminated || continue
# all "usages" (i.e. `getfield` and `isdefined` calls) are eliminated,
# now eliminate "definitions" (`setfield!`) calls
# now eliminate "definitions" (i.e. `setfield!`) calls
# (NOTE the allocation itself will be eliminated by DCE pass later)
for stmt in du.defs
stmt == newidx && continue
ir[SSAValue(stmt)][:inst] = nothing
for idx in du.defs
idx == newidx && continue # this is allocation
# verify this statement won't throw, otherwise it can't be eliminated safely
ssa = SSAValue(idx)
is_nothrow(ir, ssa) || continue
ir[ssa][:inst] = nothing
end
end
preserve_uses === nothing && continue
Expand Down
16 changes: 16 additions & 0 deletions test/compiler/irpasses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,22 @@ let # should work with constant globals
@test count(isnew, src.code) == 0
end

# don't SROA statement that may throw
# https://github.com/JuliaLang/julia/issues/48067
function issue48067(a::Int, b)
r = Ref(a)
try
setfield!(r, :x, b)
nothing
catch err
getfield(r, :x)
end
end
let src = code_typed1(issue48067, (Int,String))
@test any(iscall((src, setfield!)), src.code)
end
@test issue48067(42, "julia") == 42

# should work nicely with inlining to optimize away a complicated case
# adapted from http://wiki.luajit.org/Allocation-Sinking-Optimization#implementation%5B
struct Point
Expand Down

0 comments on commit cab9fe9

Please sign in to comment.