From 47c47af1d7015d7e77928d7a3ce2f6841ed2353a Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 31 Dec 2022 21:26:47 +0000 Subject: [PATCH] Allow irinterp to refine nothrow effect This addresses a remaining todo in the irinterp code to allow it to compute whether its particular evaluation refined `nothrow`. As a result, we can re-enable it for a larger class of ir (we had previously disabled it to avoid regressing cases where regular constprop was able to prove a `nothrow` refinement, but irinterp was not). --- base/compiler/abstractinterpretation.jl | 9 ++++--- base/compiler/optimize.jl | 2 +- base/compiler/ssair/irinterp.jl | 31 ++++++++++++++++++------- base/compiler/ssair/slot2ssa.jl | 2 +- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index c3dfc0eeb22d2..526e362b9482a 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -830,9 +830,7 @@ function concrete_eval_eligible(interp::AbstractInterpreter, if is_all_const_arg(arginfo, #=start=#2) return true else - # TODO: `is_nothrow` is not an actual requirement here, this is just a hack - # to avoid entering semi concrete eval while it doesn't properly override effects - return is_nothrow(result.effects) ? false : nothing + return false end end return nothing @@ -1004,10 +1002,11 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, ir = codeinst_to_ir(interp, code) if isa(ir, IRCode) irsv = IRInterpretationState(interp, ir, mi, sv.world, arginfo.argtypes) - rt = ir_abstract_constant_propagation(interp, irsv) + rt, nothrow = ir_abstract_constant_propagation(interp, irsv) @assert !(rt isa Conditional || rt isa MustAlias) "invalid lattice element returned from IR interpretation" if !isa(rt, Type) || typeintersect(rt, Bool) === Union{} - return ConstCallResults(rt, SemiConcreteResult(mi, ir, result.effects), result.effects, mi) + new_effects = Effects(result.effects; nothrow=nothrow) + return ConstCallResults(rt, SemiConcreteResult(mi, ir, new_effects), new_effects, mi) end end end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index ddf739d104809..c00cfd8d0c26a 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -649,7 +649,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) insert!(codelocs, idx + 1, codelocs[idx]) insert!(ssavaluetypes, idx + 1, Union{}) insert!(stmtinfo, idx + 1, NoCallInfo()) - insert!(ssaflags, idx + 1, ssaflags[idx]) + insert!(ssaflags, idx + 1, IR_FLAG_NOTHROW) if ssachangemap === nothing ssachangemap = fill(0, nstmts) end diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 717a4eec102c2..d97d5b54e9b46 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -144,9 +144,9 @@ function concrete_eval_invoke(interp::AbstractInterpreter, inst::Expr, mi::MethodInstance, irsv::IRInterpretationState) mi_cache = WorldView(code_cache(interp), irsv.world) code = get(mi_cache, mi, nothing) - code === nothing && return nothing + code === nothing && return Pair{Any, Bool}(nothing, false) argtypes = collect_argtypes(interp, inst.args[2:end], nothing, irsv.ir) - argtypes === nothing && return Union{} + argtypes === nothing && return Pair{Any, Bool}(Union{}, false) effects = decode_effects(code.ipo_purity_bits) if is_foldable(effects) && is_all_const_arg(argtypes, #=start=#1) args = collect_const_args(argtypes, #=start=#1) @@ -154,10 +154,10 @@ function concrete_eval_invoke(interp::AbstractInterpreter, value = try Core._call_in_world_total(world, args...) catch - return Union{} + return Pair{Any, Bool}(Union{}, false) end if is_inlineable_constant(value) - return Const(value) + return Pair{Any, Bool}(Const(value), true) end else ir′ = codeinst_to_ir(interp, code) @@ -166,7 +166,7 @@ function concrete_eval_invoke(interp::AbstractInterpreter, return _ir_abstract_constant_propagation(interp, irsv′) end end - return nothing + return Pair{Any, Bool}(nothing, is_nothrow(effects)) end function abstract_eval_phi_stmt(interp::AbstractInterpreter, phi::PhiNode, ::Int, irsv::IRInterpretationState) @@ -225,16 +225,21 @@ function reprocess_instruction!(interp::AbstractInterpreter, (; rt, effects) = abstract_eval_statement_expr(interp, inst, nothing, ir, irsv.mi) # All other effects already guaranteed effect free by construction if is_nothrow(effects) + ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW if isa(rt, Const) && is_inlineable_constant(rt.val) ir.stmts[idx][:inst] = quoted(rt.val) - else - ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE end end elseif head === :invoke mi′ = inst.args[1]::MethodInstance if mi′ !== irsv.mi # prevent infinite loop - rt = concrete_eval_invoke(interp, inst, mi′, irsv) + rt, nothrow = concrete_eval_invoke(interp, inst, mi′, irsv) + if nothrow + ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + if isa(rt, Const) && is_inlineable_constant(rt.val) + ir.stmts[idx][:inst] = quoted(rt.val) + end + end end elseif head === :throw_undef_if_not || # TODO: Terminate interpretation early if known false? head === :gc_preserve_begin || @@ -416,7 +421,15 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR end end - return maybe_singleton_const(ultimate_rt) + nothrow = true + for i = 1:length(ir.stmts) + if (ir.stmts[i][:flag] & IR_FLAG_NOTHROW) == 0 + nothrow = false + break + end + end + + return Pair{Any, Bool}(maybe_singleton_const(ultimate_rt), nothrow) end function ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IRInterpretationState) diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 79bdf817dc866..289cea14dc01d 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -203,7 +203,7 @@ function strip_trailing_junk!(ci::CodeInfo, code::Vector{Any}, info::Vector{Call push!(ssavaluetypes, Union{}) push!(codelocs, 0) push!(info, NoCallInfo()) - push!(ssaflags, IR_FLAG_NULL) + push!(ssaflags, IR_FLAG_NOTHROW) end nothing end