Skip to content

Hook up IR_FLAG_NOTHROW #45752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 44 additions & 35 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -188,81 +188,90 @@ function stmt_affects_purity(@nospecialize(stmt), ir)
end

"""
stmt_effect_free(stmt, rt, src::Union{IRCode,IncrementalCompact})
stmt_effect_flags(stmt, rt, src::Union{IRCode,IncrementalCompact})

Determine whether a `stmt` is "side-effect-free", i.e. may be removed if it has no uses.
Returns a tuple of (effect_free_and_nothrow, nothrow) for a given statement.
"""
function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src::Union{IRCode,IncrementalCompact})
isa(stmt, PiNode) && return true
isa(stmt, PhiNode) && return true
isa(stmt, ReturnNode) && return false
isa(stmt, GotoNode) && return false
isa(stmt, GotoIfNot) && return false
isa(stmt, Slot) && return false # Slots shouldn't occur in the IR at this point, but let's be defensive here
isa(stmt, GlobalRef) && return isdefined(stmt.mod, stmt.name)
function stmt_effect_flags(@nospecialize(stmt), @nospecialize(rt), src::Union{IRCode,IncrementalCompact})
# TODO: We're duplicating analysis from inference here.
isa(stmt, PiNode) && return (true, true)
isa(stmt, PhiNode) && return (true, true)
isa(stmt, ReturnNode) && return (false, true)
isa(stmt, GotoNode) && return (false, true)
isa(stmt, GotoIfNot) && return (false, argextype(stmt.cond, src) ⊑ Bool)
isa(stmt, Slot) && return (false, false) # Slots shouldn't occur in the IR at this point, but let's be defensive here
if isa(stmt, GlobalRef)
nothrow = isdefined(stmt.mod, stmt.name)
return (nothrow, nothrow)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return (nothrow, nothrow)
return (nothrow && isconst(stmt.mod, stmt.name), nothrow)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should keep doing what it did before, but probably the total in the comment is from and it should actually be effect_free. This code predates the clarification of those terms.

end
if isa(stmt, Expr)
(; head, args) = stmt
if head === :static_parameter
etyp = (isa(src, IRCode) ? src.sptypes : src.ir.sptypes)[args[1]::Int]
# if we aren't certain enough about the type, it might be an UndefVarError at runtime
return isa(etyp, Const)
nothrow = isa(etyp, Const)
return (nothrow, nothrow)
end
if head === :call
f = argextype(args[1], src)
f = singleton_type(f)
f === nothing && return false
f === nothing && return (false, false)
if isa(f, IntrinsicFunction)
intrinsic_effect_free_if_nothrow(f) || return false
return intrinsic_nothrow(f,
Any[argextype(args[i], src) for i = 2:length(args)])
nothrow = intrinsic_nothrow(f,
Any[argextype(args[i], src) for i = 2:length(args)])
nothrow || return (false, false)
return (intrinsic_effect_free_if_nothrow(f), nothrow)
end
contains_is(_PURE_BUILTINS, f) && return true
contains_is(_PURE_BUILTINS, f) && return (true, true)
# `get_binding_type` sets the type to Any if the binding doesn't exist yet
if f === Core.get_binding_type
length(args) == 3 || return false
M, s = argextype(args[2], src), argextype(args[3], src)
return get_binding_type_effect_free(M, s)
total = get_binding_type_effect_free(M, s)
return (total, total)
end
contains_is(_EFFECT_FREE_BUILTINS, f) || return false
rt === Bottom && return false
return _builtin_nothrow(f, Any[argextype(args[i], src) for i = 2:length(args)], rt)
rt === Bottom && return (false, false)
nothrow = _builtin_nothrow(f, Any[argextype(args[i], src) for i = 2:length(args)], rt)
nothrow || return (false, false)
return (contains_is(_EFFECT_FREE_BUILTINS, f), nothrow)
elseif head === :new
typ = argextype(args[1], src)
# `Expr(:new)` of unknown type could raise arbitrary TypeError.
typ, isexact = instanceof_tfunc(typ)
isexact || return false
isconcretedispatch(typ) || return false
isexact || return (false, false)
isconcretedispatch(typ) || return (false, false)
typ = typ::DataType
fieldcount(typ) >= length(args) - 1 || return false
fieldcount(typ) >= length(args) - 1 || return (false, false)
for fld_idx in 1:(length(args) - 1)
eT = argextype(args[fld_idx + 1], src)
fT = fieldtype(typ, fld_idx)
eT ⊑ fT || return false
eT ⊑ fT || return (false, false)
end
return true
return (true, true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return (true, true)
return (!ismutabletype(typ), true)

elseif head === :foreigncall
return foreigncall_effect_free(stmt, src)
total = foreigncall_effect_free(stmt, src)
return (total, total)
elseif head === :new_opaque_closure
length(args) < 4 && return false
length(args) < 4 && return (false, false)
typ = argextype(args[1], src)
typ, isexact = instanceof_tfunc(typ)
isexact || return false
typ ⊑ Tuple || return false
isexact || return (false, false)
typ ⊑ Tuple || return (false, false)
rt_lb = argextype(args[2], src)
rt_ub = argextype(args[3], src)
src = argextype(args[4], src)
if !(rt_lb ⊑ Type && rt_ub ⊑ Type && src ⊑ Method)
return false
return (false, false)
end
return true
return (true, true)
elseif head === :isdefined || head === :the_exception || head === :copyast || head === :inbounds || head === :boundscheck
return true
return (true, true)
else
# e.g. :loopinfo
return false
return (false, false)
end
end
return true
return (true, true)
end

function foreigncall_effect_free(stmt::Expr, src::Union{IRCode,IncrementalCompact})
Expand Down Expand Up @@ -421,7 +430,7 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState,
for i in 1:length(ir.stmts)
node = ir.stmts[i]
stmt = node[:inst]
if stmt_affects_purity(stmt, ir) && !stmt_effect_free(stmt, node[:type], ir)
if stmt_affects_purity(stmt, ir) && !stmt_effect_flags(stmt, node[:type], ir)[1]
proven_pure = false
break
end
Expand Down
4 changes: 2 additions & 2 deletions base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import Core.Compiler: # Core.Compiler specific definitions
isbitstype, isexpr, is_meta_expr_head, println, widenconst, argextype, singleton_type,
fieldcount_noerror, try_compute_field, try_compute_fieldidx, hasintersect, ⊑,
intrinsic_nothrow, array_builtin_common_typecheck, arrayset_typecheck,
setfield!_nothrow, alloc_array_ndims, stmt_effect_free, check_effect_free!
setfield!_nothrow, alloc_array_ndims, check_effect_free!

include(x) = _TOP_MOD.include(@__MODULE__, x)
if _TOP_MOD === Core.Compiler
Expand Down Expand Up @@ -1333,7 +1333,7 @@ function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any})
return # ThrownEscape is already checked
else
# we escape statements with the `ThrownEscape` property using the effect-freeness
# computed by `stmt_effect_free` invoked within inlining
# computed by `stmt_effect_flags` invoked within inlining
# TODO throwness ≠ "effect-free-ness"
if is_effect_free(astate.ir, pc)
add_liveness_changes!(astate, pc, args, 2)
Expand Down
14 changes: 9 additions & 5 deletions base/compiler/ssair/inlining.jl
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,9 @@ function handle_single_case!(
stmt.head = :invoke
pushfirst!(stmt.args, case.invoke)
if is_removable_if_unused(case.effects)
ir[SSAValue(idx)][:flag] |= IR_FLAG_EFFECT_FREE
ir[SSAValue(idx)][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW
elseif is_nothrow(case.effects)
ir[SSAValue(idx)][:flag] |= IR_FLAG_NOTHROW
end
elseif case === nothing
# Do, well, nothing
Expand Down Expand Up @@ -1138,11 +1140,13 @@ end
# For primitives, we do that right here. For proper calls, we will
# discover this when we consult the caches.
function check_effect_free!(ir::IRCode, idx::Int, @nospecialize(stmt), @nospecialize(rt))
if stmt_effect_free(stmt, rt, ir)
ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE
return true
(total, nothrow) = stmt_effect_flags(stmt, rt, ir)
if total
ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW
elseif nothrow
ir.stmts[idx][:flag] |= IR_FLAG_NOTHROW
end
return false
return total
end

# Handles all analysis and inlining of intrinsics and builtins. In particular,
Expand Down
20 changes: 14 additions & 6 deletions base/compiler/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ function setindex!(is::InstructionStream, newval::Instruction, idx::Int)
is.flag[idx] = newval[:flag]
return is
end
function setindex!(is::InstructionStream, newval::AnySSAValue, idx::Int)
function setindex!(is::InstructionStream, newval::Union{AnySSAValue, Nothing}, idx::Int)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems unnecessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regressed at some point. It's the idiom for deleting and instruction so should work, but yes, it's mostly unrelated to this PR.

is.inst[idx] = newval
return is
end
Expand Down Expand Up @@ -343,7 +343,7 @@ function getindex(x::IRCode, s::SSAValue)
end
end

function setindex!(x::IRCode, repl::Union{Instruction, AnySSAValue}, s::SSAValue)
function setindex!(x::IRCode, repl::Union{Instruction, Nothing, AnySSAValue}, s::SSAValue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

if s.id <= length(x.stmts)
x.stmts[s.id] = repl
else
Expand Down Expand Up @@ -509,8 +509,11 @@ function insert_node!(ir::IRCode, pos::Int, inst::NewInstruction, attach_after::
node[:line] = something(inst.line, ir.stmts[pos][:line])
flag = inst.flag
if !inst.effect_free_computed
if stmt_effect_free(inst.stmt, inst.type, ir)
flag |= IR_FLAG_EFFECT_FREE
(effect_free_and_nothrow, nothrow) = stmt_effect_flags(inst.stmt, inst.type, ir)
if effect_free_and_nothrow
flag |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW
elseif nothrow
flag |= IR_FLAG_NOTHROW
end
end
node[:inst], node[:type], node[:flag] = inst.stmt, inst.type, flag
Expand Down Expand Up @@ -830,8 +833,13 @@ function insert_node_here!(compact::IncrementalCompact, inst::NewInstruction, re
resize!(compact, result_idx)
end
flag = inst.flag
if !inst.effect_free_computed && stmt_effect_free(inst.stmt, inst.type, compact)
flag |= IR_FLAG_EFFECT_FREE
if !inst.effect_free_computed
(effect_free_and_nothrow, nothrow) = stmt_effect_flags(inst.stmt, inst.type, compact)
if effect_free_and_nothrow
flag |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW
elseif nothrow
flag |= IR_FLAG_NOTHROW
end
end
node = compact.result[result_idx]
node[:inst], node[:type], node[:line], node[:flag] = inst.stmt, inst.type, inst.line, flag
Expand Down
4 changes: 4 additions & 0 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,10 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ
return argtypes[1] ⊑ Module && argtypes[2] ⊑ Symbol
elseif f === donotdelete
return true
elseif f === Core.finalizer
2 <= length(argtypes) <= 4 || return false
# Core.finalizer does no error checking - that's done in Base.finalizer
return true
end
return false
end
Expand Down