diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 580b307838110..1e812db13c9eb 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -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) + 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) 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}) @@ -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 diff --git a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl index 407b447a228a3..272ea0e8edbbc 100644 --- a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl +++ b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl @@ -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 @@ -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) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 6b3c5b2e44c34..ca9eb818f0edb 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -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 @@ -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, diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 3d8c0b17c1725..93ca66cb7c931 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -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) is.inst[idx] = newval return is end @@ -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) if s.id <= length(x.stmts) x.stmts[s.id] = repl else @@ -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 @@ -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 diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 1a0ac0539b307..096e47c1e500d 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -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