@@ -653,14 +653,109 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
653653 # this edge is known to terminate
654654 edge_effects = Effects (edge_effects; terminates= ALWAYS_TRUE)
655655 elseif edgecycle
656- # Some sort of recursion was detected. Even if we did not limit types,
657- # we cannot guarantee that the call will terminate
658- edge_effects = Effects (edge_effects; terminates= TRISTATE_UNKNOWN)
656+ # Some sort of recursion was detected.
657+ if edge != = nothing && ! edgelimited && ! is_edge_recursed (edge, sv)
658+ # no `MethodInstance` cycles -- don't taint :terminate
659+ else
660+ # we cannot guarantee that the call will terminate
661+ effects = Effects (effects; terminates= TRISTATE_UNKNOWN)
662+ end
659663 end
660664 return MethodCallResult (rt, edgecycle, edgelimited, edge, edge_effects)
661665end
662666
663- # keeps result and context information of abstract method call, will be used by succeeding constant-propagation
667+ function edge_matches_sv (frame:: InferenceState , method:: Method , @nospecialize (sig), sparams:: SimpleVector , hardlimit:: Bool , sv:: InferenceState )
668+ # The `method_for_inference_heuristics` will expand the given method's generator if
669+ # necessary in order to retrieve this field from the generated `CodeInfo`, if it exists.
670+ # The other `CodeInfo`s we inspect will already have this field inflated, so we just
671+ # access it directly instead (to avoid regeneration).
672+ callee_method2 = method_for_inference_heuristics (method, sig, sparams) # Union{Method, Nothing}
673+
674+ inf_method2 = frame. src. method_for_inference_limit_heuristics # limit only if user token match
675+ inf_method2 isa Method || (inf_method2 = nothing )
676+ if callee_method2 != = inf_method2
677+ return false
678+ end
679+ if ! hardlimit
680+ # if this is a soft limit,
681+ # also inspect the parent of this edge,
682+ # to see if they are the same Method as sv
683+ # in which case we'll need to ensure it is convergent
684+ # otherwise, we don't
685+
686+ # check in the cycle list first
687+ # all items in here are mutual parents of all others
688+ if ! _any (p:: InferenceState -> matches_sv (p, sv), frame. callers_in_cycle)
689+ let parent = frame. parent
690+ parent != = nothing || return false
691+ parent = parent:: InferenceState
692+ (parent. cached || parent. parent != = nothing ) || return false
693+ matches_sv (parent, sv) || return false
694+ end
695+ end
696+
697+ # If the method defines a recursion relation, give it a chance
698+ # to tell us that this recursion is actually ok.
699+ if isdefined (method, :recursion_relation )
700+ if Core. _apply_pure (method. recursion_relation, Any[method, callee_method2, sig, frame. linfo. specTypes])
701+ return false
702+ end
703+ end
704+ end
705+ return true
706+ end
707+
708+ # This function is used for computing alternate limit heuristics
709+ function method_for_inference_heuristics (method:: Method , @nospecialize (sig), sparams:: SimpleVector )
710+ if isdefined (method, :generator ) && method. generator. expand_early && may_invoke_generator (method, sig, sparams)
711+ method_instance = specialize_method (method, sig, sparams)
712+ if isa (method_instance, MethodInstance)
713+ cinfo = get_staged (method_instance)
714+ if isa (cinfo, CodeInfo)
715+ method2 = cinfo. method_for_inference_limit_heuristics
716+ if method2 isa Method
717+ return method2
718+ end
719+ end
720+ end
721+ end
722+ return nothing
723+ end
724+
725+ function matches_sv (parent:: InferenceState , sv:: InferenceState )
726+ sv_method2 = sv. src. method_for_inference_limit_heuristics # limit only if user token match
727+ sv_method2 isa Method || (sv_method2 = nothing )
728+ parent_method2 = parent. src. method_for_inference_limit_heuristics # limit only if user token match
729+ parent_method2 isa Method || (parent_method2 = nothing )
730+ return parent. linfo. def === sv. linfo. def && sv_method2 === parent_method2
731+ end
732+
733+ function is_edge_recursed (edge:: MethodInstance , sv:: InferenceState )
734+ return any (InfStackUnwind (sv)) do infstate
735+ return edge === infstate. linfo
736+ end
737+ end
738+
739+ function is_method_recursed (method:: Method , sv:: InferenceState )
740+ return any (InfStackUnwind (sv)) do infstate
741+ return method === infstate. linfo. def
742+ end
743+ end
744+
745+ function is_constprop_edge_recursed (edge:: MethodInstance , sv:: InferenceState )
746+ return any (InfStackUnwind (sv)) do infstate
747+ return edge === infstate. linfo && any (infstate. result. overridden_by_const)
748+ end
749+ end
750+
751+ function is_constprop_method_recursed (method:: Method , sv:: InferenceState )
752+ return any (InfStackUnwind (sv)) do infstate
753+ return method === infstate. linfo. def && any (infstate. result. overridden_by_const)
754+ end
755+ end
756+
757+ # keeps result and context information of abstract_method_call, which will later be used for
758+ # backedge computation, and concrete evaluation or constant-propagation
664759struct MethodCallResult
665760 rt
666761 edgecycle:: Bool
@@ -802,17 +897,14 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, resul
802897 if inf_result === nothing
803898 # if there might be a cycle, check to make sure we don't end up
804899 # calling ourselves here.
805- let result = result # prevent capturing
806- if result. edgecycle && _any (InfStackUnwind (sv)) do infstate
807- # if the type complexity limiting didn't decide to limit the call signature (`result.edgelimited = false`)
808- # we can relax the cycle detection by comparing `MethodInstance`s and allow inference to
809- # propagate different constant elements if the recursion is finite over the lattice
810- return (result. edgelimited ? match. method === infstate. linfo. def : mi === infstate. linfo) &&
811- any (infstate. result. overridden_by_const)
812- end
813- add_remark! (interp, sv, " [constprop] Edge cycle encountered" )
814- return nothing
815- end
900+ if result. edgecycle && (result. edgelimited ?
901+ is_constprop_method_recursed (match. method, sv) :
902+ # if the type complexity limiting didn't decide to limit the call signature (`result.edgelimited = false`)
903+ # we can relax the cycle detection by comparing `MethodInstance`s and allow inference to
904+ # propagate different constant elements if the recursion is finite over the lattice
905+ is_constprop_edge_recursed (mi, sv))
906+ add_remark! (interp, sv, " [constprop] Edge cycle encountered" )
907+ return nothing
816908 end
817909 inf_result = InferenceResult (mi, (arginfo, sv))
818910 if ! any (inf_result. overridden_by_const)
@@ -923,8 +1015,8 @@ function is_const_prop_profitable_arg(@nospecialize(arg))
9231015 isa (arg, PartialOpaque) && return true
9241016 isa (arg, Const) || return true
9251017 val = arg. val
926- # don't consider mutable values or Strings useful constants
927- return isa (val, Symbol) || isa (val, Type) || ( ! isa (val, String) && ! ismutable (val) )
1018+ # don't consider mutable values useful constants
1019+ return isa (val, Symbol) || isa (val, Type) || ! ismutable (val)
9281020end
9291021
9301022function is_const_prop_profitable_conditional (cnd:: Conditional , fargs:: Vector{Any} , sv:: InferenceState )
@@ -1276,7 +1368,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::
12761368 end
12771369 cti = Any[Vararg{argt}]
12781370 end
1279- if _any (t -> t === Bottom, cti)
1371+ if any ( @nospecialize (t) -> t === Bottom, cti)
12801372 continue
12811373 end
12821374 for j = 1 : length (ctypes)
@@ -1951,6 +2043,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
19512043 for i = 3 : length (e. args)
19522044 if abstract_eval_value (interp, e. args[i], vtypes, sv) === Bottom
19532045 t = Bottom
2046+ tristate_merge! (sv, EFFECTS_THROWS)
2047+ @goto t_computed
19542048 end
19552049 end
19562050 cconv = e. args[5 ]
0 commit comments