Skip to content

Commit

Permalink
effects: relax recursion detection for effects analysis
Browse files Browse the repository at this point in the history
In a similar spirit to #40561, we can relax the recursion detection to
guarantee `:terminates` effect and allow the effects analysis to not
taint `:terminates` effect when there are no cycles in `MethodInstance`s
in a call graph.

fix #45781
  • Loading branch information
aviatesk committed Jul 20, 2022
1 parent c882602 commit c65f21b
Showing 1 changed file with 41 additions and 15 deletions.
56 changes: 41 additions & 15 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -653,14 +653,43 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
# this edge is known to terminate
edge_effects = Effects(edge_effects; terminates=ALWAYS_TRUE)
elseif edgecycle
# Some sort of recursion was detected. Even if we did not limit types,
# we cannot guarantee that the call will terminate
edge_effects = Effects(edge_effects; terminates=TRISTATE_UNKNOWN)
# Some sort of recursion was detected.
if edge !== nothing && !edgelimited && !is_edge_recursed(edge, sv)
# no `MethodInstance` cycles -- don't taint :terminate
else
# we cannot guarantee that the call will terminate
edge_effects = Effects(edge_effects; terminates=ALWAYS_FALSE)
end
end
return MethodCallResult(rt, edgecycle, edgelimited, edge, edge_effects)
end

# keeps result and context information of abstract method call, will be used by succeeding constant-propagation
function is_edge_recursed(edge::MethodInstance, sv::InferenceState)
return any(InfStackUnwind(sv)) do infstate
return edge === infstate.linfo
end
end

function is_method_recursed(method::Method, sv::InferenceState)
return any(InfStackUnwind(sv)) do infstate
return method === infstate.linfo.def
end
end

function is_constprop_edge_recursed(edge::MethodInstance, sv::InferenceState)
return any(InfStackUnwind(sv)) do infstate
return edge === infstate.linfo && any(infstate.result.overridden_by_const)
end
end

function is_constprop_method_recursed(method::Method, sv::InferenceState)
return any(InfStackUnwind(sv)) do infstate
return method === infstate.linfo.def && any(infstate.result.overridden_by_const)
end
end

# keeps result and context information of abstract_method_call, which will later be used for
# backedge computation, and concrete evaluation or constant-propagation
struct MethodCallResult
rt
edgecycle::Bool
Expand Down Expand Up @@ -802,17 +831,14 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, resul
if inf_result === nothing
# if there might be a cycle, check to make sure we don't end up
# calling ourselves here.
let result = result # prevent capturing
if result.edgecycle && _any(InfStackUnwind(sv)) do infstate
# if the type complexity limiting didn't decide to limit the call signature (`result.edgelimited = false`)
# we can relax the cycle detection by comparing `MethodInstance`s and allow inference to
# propagate different constant elements if the recursion is finite over the lattice
return (result.edgelimited ? match.method === infstate.linfo.def : mi === infstate.linfo) &&
any(infstate.result.overridden_by_const)
end
add_remark!(interp, sv, "[constprop] Edge cycle encountered")
return nothing
end
if result.edgecycle && (result.edgelimited ?
is_constprop_method_recursed(match.method, sv) :
# if the type complexity limiting didn't decide to limit the call signature (`result.edgelimited = false`)
# we can relax the cycle detection by comparing `MethodInstance`s and allow inference to
# propagate different constant elements if the recursion is finite over the lattice
is_constprop_edge_recursed(mi, sv))
add_remark!(interp, sv, "[constprop] Edge cycle encountered")
return nothing
end
inf_result = InferenceResult(mi, (arginfo, sv))
if !any(inf_result.overridden_by_const)
Expand Down

0 comments on commit c65f21b

Please sign in to comment.