Skip to content
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

Refine effects based on optimizer-derived information #50805

Merged
merged 14 commits into from
Aug 31, 2023
Merged
151 changes: 69 additions & 82 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -464,13 +464,6 @@ function add_call_backedges!(interp::AbstractInterpreter, @nospecialize(rettype)
if !isoverlayed(method_table(interp))
all_effects = Effects(all_effects; nonoverlayed=false)
end
if (# ignore the `:noinbounds` property if `:consistent`-cy is tainted already
(sv isa InferenceState && sv.ipo_effects.consistent === ALWAYS_FALSE) ||
all_effects.consistent === ALWAYS_FALSE ||
# or this `:noinbounds` doesn't taint it
!stmt_taints_inbounds_consistency(sv))
all_effects = Effects(all_effects; noinbounds=false)
end
all_effects === Effects() && return nothing
end
for edge in edges
Expand Down Expand Up @@ -785,6 +778,7 @@ end
function abstract_call_method_with_const_args(interp::AbstractInterpreter,
result::MethodCallResult, @nospecialize(f), arginfo::ArgInfo, si::StmtInfo,
match::MethodMatch, sv::AbsIntState, invokecall::Union{Nothing,InvokeCall}=nothing)

if !const_prop_enabled(interp, sv, match)
return nothing
end
Expand Down Expand Up @@ -853,12 +847,6 @@ function concrete_eval_eligible(interp::AbstractInterpreter,
return :none
end
end
if !effects.noinbounds && stmt_taints_inbounds_consistency(sv)
# If the current statement is @inbounds or we propagate inbounds,
# the call's :consistent-cy is tainted and not consteval eligible.
add_remark!(interp, sv, "[constprop] Concrete evel disabled for inbounds")
return :none
end
mi = result.edge
if mi !== nothing && is_foldable(effects)
if f !== nothing && is_all_const_arg(arginfo, #=start=#2)
Expand Down Expand Up @@ -1162,7 +1150,7 @@ function semi_concrete_eval_call(interp::AbstractInterpreter,
irsv = IRInterpretationState(interp, code, mi, arginfo.argtypes, world)
if irsv !== nothing
irsv.parent = sv
rt, nothrow = ir_abstract_constant_propagation(interp, irsv)
rt, (nothrow, noub) = ir_abstract_constant_propagation(interp, irsv)
@assert !(rt isa Conditional || rt isa MustAlias) "invalid lattice element returned from irinterp"
if !(isa(rt, Type) && hasintersect(rt, Bool))
ir = irsv.ir
Expand All @@ -1174,6 +1162,9 @@ function semi_concrete_eval_call(interp::AbstractInterpreter,
if !is_nothrow(effects)
effects = Effects(effects; nothrow)
end
if noub
effects = Effects(effects; noub = ALWAYS_TRUE)
end
return ConstCallResults(rt, SemiConcreteResult(mi, ir, effects), effects, mi)
end
end
Expand Down Expand Up @@ -1999,17 +1990,6 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f),
end
rt = abstract_call_builtin(interp, f, arginfo, sv)
effects = builtin_effects(𝕃ᵢ, f, arginfo, rt)
if (isa(sv, InferenceState) && f === getfield && fargs !== nothing &&
isexpr(fargs[end], :boundscheck) && !is_nothrow(effects))
# As a special case, we delayed tainting `noinbounds` for `getfield` calls
# in case we can prove in-boundedness indepedently.
# Here we need to put that back in other cases.
# N.B. This isn't about the effects of the call itself,
# but a delayed contribution of the :boundscheck statement,
# so we need to merge this directly into sv, rather than modifying the effects.
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; noinbounds=false,
consistent = iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) ? ALWAYS_TRUE : ALWAYS_FALSE))
end
return CallMeta(rt, effects, NoCallInfo())
elseif isa(f, Core.OpaqueClosure)
# calling an OpaqueClosure about which we have no information returns no information
Expand Down Expand Up @@ -2226,24 +2206,7 @@ function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow))
return rt
elseif head === :boundscheck
if isa(sv, InferenceState)
stmt = sv.src.code[sv.currpc]
if isexpr(stmt, :call)
f = abstract_eval_value(interp, stmt.args[1], vtypes, sv)
if f isa Const && f.val === getfield
# boundscheck of `getfield` call is analyzed by tfunc potentially without
# tainting :noinbounds or :noub when it's known to be nothrow
return Bool
end
end
# If there is no particular `@inbounds` for this function, then we only taint `:noinbounds`,
# which will subsequently taint `:consistent` if this function is called from another
# function that uses `@inbounds`. However, if this `:boundscheck` is itself within an
# `@inbounds` region, its value depends on `--check-bounds`, so we need to taint
# `:consistent`-cy here also.
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; noinbounds=false,
consistent = iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) ? ALWAYS_TRUE : ALWAYS_FALSE))
end
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent = ALWAYS_FALSE))
return Bool
elseif head === :inbounds
@assert false "Expected `:inbounds` expression to have been moved into SSA flags"
Expand Down Expand Up @@ -2328,6 +2291,11 @@ function mark_curr_effect_flags!(sv::AbsIntState, effects::Effects)
else
sub_curr_ssaflag!(sv, IR_FLAG_CONSISTENT)
end
if is_noub(effects, false)
add_curr_ssaflag!(sv, IR_FLAG_NOUB)
else
sub_curr_ssaflag!(sv, IR_FLAG_NOUB)
end
end
end

Expand Down Expand Up @@ -2364,8 +2332,8 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
elseif ehead === :new
t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))
ut = unwrap_unionall(t)
consistent = ALWAYS_FALSE
nothrow = noub = false
consistent = noub = ALWAYS_FALSE
nothrow = false
if isa(ut, DataType) && !isabstracttype(ut)
ismutable = ismutabletype(ut)
fcount = datatype_fieldcount(ut)
Expand All @@ -2378,10 +2346,10 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
# mutable object isn't `:consistent`, but we still have a chance that
# return type information later refines the `:consistent`-cy of the method
consistent = CONSISTENT_IF_NOTRETURNED
noub = true
noub = ALWAYS_TRUE
else
consistent = ALWAYS_TRUE
noub = true
noub = ALWAYS_TRUE
end
if isconcretedispatch(t)
nothrow = true
Expand Down Expand Up @@ -2529,6 +2497,20 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
end
end
end
elseif ehead === :throw_undef_if_not
condt = argextype(stmt.args[2], ir)
condval = maybe_extract_const_bool(condt)
t = Nothing
effects = EFFECTS_THROWS
if condval isa Bool
if condval
effects = EFFECTS_TOTAL
else
t = Union{}
end
elseif !hasintersect(windenconst(condt), Bool)
t = Union{}
end
elseif false
@label always_throw
t = Bottom
Expand Down Expand Up @@ -2575,7 +2557,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes:
terminates = override.terminates_globally ? true : effects.terminates,
notaskstate = override.notaskstate ? true : effects.notaskstate,
inaccessiblememonly = override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly,
noub = override.noub ? true : effects.noub)
noub = override.noub ? ALWAYS_TRUE : effects.noub)
end
return RTEffects(t, effects)
end
Expand Down Expand Up @@ -2604,14 +2586,13 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
return abstract_eval_special_value(interp, e, vtypes, sv)
end
(; rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv)
if !effects.noinbounds
if !propagate_inbounds(sv)
if effects.noub === NOUB_IF_NOINBOUNDS
if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS)
effects = Effects(effects; noub=ALWAYS_FALSE)
elseif !propagate_inbounds(sv)
# The callee read our inbounds flag, but unless we propagate inbounds,
# we ourselves don't read our parent's inbounds.
effects = Effects(effects; noinbounds=true)
end
if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS)
effects = Effects(effects; consistent=ALWAYS_FALSE, noub=false)
effects = Effects(effects; noub=ALWAYS_TRUE)
end
end
merge_effects!(interp, sv, effects)
Expand Down Expand Up @@ -2667,6 +2648,10 @@ function handle_global_assignment!(interp::AbstractInterpreter, frame::Inference
effect_free = ALWAYS_FALSE
nothrow = global_assignment_nothrow(lhs.mod, lhs.name, newty)
inaccessiblememonly = ALWAYS_FALSE
if !nothrow
sub_curr_ssaflag!(frame, IR_FLAG_NOTHROW)
end
sub_curr_ssaflag!(frame, IR_FLAG_EFFECT_FREE)
merge_effects!(interp, frame, Effects(EFFECTS_TOTAL; effect_free, nothrow, inaccessiblememonly))
return nothing
end
Expand Down Expand Up @@ -3004,7 +2989,12 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
end
condval = maybe_extract_const_bool(condt)
nothrow = (condval !== nothing) || ⊑(𝕃ᵢ, orig_condt, Bool)
nothrow && add_curr_ssaflag!(frame, IR_FLAG_NOTHROW)
if nothrow
add_curr_ssaflag!(frame, IR_FLAG_NOTHROW)
else
merge_effects!(interp, frame, EFFECTS_THROWS)
end

if !isempty(frame.pclimitations)
# we can't model the possible effect of control
# dependencies on the return
Expand All @@ -3016,6 +3006,11 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
if condval === true
@goto fallthrough
else
if !nothrow && !hasintersect(widenconst(orig_condt), Bool)
ssavaluetypes[currpc] = Bottom
@goto find_next_bb
end

succs = bbs[currbb].succs
if length(succs) == 1
@assert condval === false || (stmt.dest === currpc + 1)
Expand All @@ -3029,38 +3024,30 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
nextbb = falsebb
handle_control_backedge!(interp, frame, currpc, stmt.dest)
@goto branch
else
if !nothrow
merge_effects!(interp, frame, EFFECTS_THROWS)
if !hasintersect(widenconst(orig_condt), Bool)
ssavaluetypes[currpc] = Bottom
@goto find_next_bb
end
end
end

# We continue with the true branch, but process the false
# branch here.
if isa(condt, Conditional)
else_change = conditional_change(𝕃ᵢ, currstate, condt.elsetype, condt.slot)
if else_change !== nothing
false_vartable = stoverwrite1!(copy(currstate), else_change)
else
false_vartable = currstate
end
changed = update_bbstate!(𝕃ᵢ, frame, falsebb, false_vartable)
then_change = conditional_change(𝕃ᵢ, currstate, condt.thentype, condt.slot)
if then_change !== nothing
stoverwrite1!(currstate, then_change)
end
# We continue with the true branch, but process the false
# branch here.
if isa(condt, Conditional)
else_change = conditional_change(𝕃ᵢ, currstate, condt.elsetype, condt.slot)
if else_change !== nothing
false_vartable = stoverwrite1!(copy(currstate), else_change)
else
changed = update_bbstate!(𝕃ᵢ, frame, falsebb, currstate)
false_vartable = currstate
end
if changed
handle_control_backedge!(interp, frame, currpc, stmt.dest)
push!(W, falsebb)
changed = update_bbstate!(𝕃ᵢ, frame, falsebb, false_vartable)
then_change = conditional_change(𝕃ᵢ, currstate, condt.thentype, condt.slot)
if then_change !== nothing
stoverwrite1!(currstate, then_change)
end
@goto fallthrough
else
changed = update_bbstate!(𝕃ᵢ, frame, falsebb, currstate)
end
if changed
handle_control_backedge!(interp, frame, currpc, stmt.dest)
push!(W, falsebb)
end
@goto fallthrough
end
elseif isa(stmt, ReturnNode)
rt = abstract_eval_value(interp, stmt.val, currstate, frame)
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/bootstrap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let interp = NativeInterpreter()
fs = Any[
# we first create caches for the optimizer, because they contain many loop constructions
# and they're better to not run in interpreter even during bootstrapping
#=analyze_escapes_tt,=# run_passes,
#=analyze_escapes_tt,=# run_passes_ipo_safe,
# then we create caches for inference entries
typeinf_ext, typeinf, typeinf_edge,
]
Expand Down
Loading