@@ -653,14 +653,109 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
653
653
# this edge is known to terminate
654
654
edge_effects = Effects (edge_effects; terminates= ALWAYS_TRUE)
655
655
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
659
663
end
660
664
return MethodCallResult (rt, edgecycle, edgelimited, edge, edge_effects)
661
665
end
662
666
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
664
759
struct MethodCallResult
665
760
rt
666
761
edgecycle:: Bool
@@ -802,17 +897,14 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, resul
802
897
if inf_result === nothing
803
898
# if there might be a cycle, check to make sure we don't end up
804
899
# 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
816
908
end
817
909
inf_result = InferenceResult (mi, (arginfo, sv))
818
910
if ! any (inf_result. overridden_by_const)
@@ -923,8 +1015,8 @@ function is_const_prop_profitable_arg(@nospecialize(arg))
923
1015
isa (arg, PartialOpaque) && return true
924
1016
isa (arg, Const) || return true
925
1017
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)
928
1020
end
929
1021
930
1022
function 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::
1276
1368
end
1277
1369
cti = Any[Vararg{argt}]
1278
1370
end
1279
- if _any (t -> t === Bottom, cti)
1371
+ if any ( @nospecialize (t) -> t === Bottom, cti)
1280
1372
continue
1281
1373
end
1282
1374
for j = 1 : length (ctypes)
@@ -1951,6 +2043,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
1951
2043
for i = 3 : length (e. args)
1952
2044
if abstract_eval_value (interp, e. args[i], vtypes, sv) === Bottom
1953
2045
t = Bottom
2046
+ tristate_merge! (sv, EFFECTS_THROWS)
2047
+ @goto t_computed
1954
2048
end
1955
2049
end
1956
2050
cconv = e. args[5 ]
0 commit comments