@@ -310,6 +310,7 @@ function add_call_backedges!(interp::AbstractInterpreter,
310
310
end
311
311
312
312
const RECURSION_UNUSED_MSG = " Bounded recursion detected with unused result. Annotated return type may be wider than true result."
313
+ const RECURSION_MSG = " Bounded recursion detected. Call was widened to force convergence."
313
314
314
315
function abstract_call_method (interp:: AbstractInterpreter , method:: Method , @nospecialize (sig), sparams:: SimpleVector , hardlimit:: Bool , sv:: InferenceState )
315
316
if method. name === :depwarn && isdefined (Main, :Base ) && method. module === Main. Base
@@ -321,18 +322,57 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
321
322
# look through the parents list to see if there's a call to the same method
322
323
# and from the same method.
323
324
# Returns the topmost occurrence of that repeated edge.
324
- cyclei = 0
325
- infstate = sv
326
325
edgecycle = false
327
326
# The `method_for_inference_heuristics` will expand the given method's generator if
328
327
# necessary in order to retrieve this field from the generated `CodeInfo`, if it exists.
329
328
# The other `CodeInfo`s we inspect will already have this field inflated, so we just
330
329
# access it directly instead (to avoid regeneration).
331
- method2 = method_for_inference_heuristics (method, sig, sparams) # Union{Method, Nothing}
330
+ callee_method2 = method_for_inference_heuristics (method, sig, sparams) # Union{Method, Nothing}
332
331
sv_method2 = sv. src. method_for_inference_limit_heuristics # limit only if user token match
333
332
sv_method2 isa Method || (sv_method2 = nothing ) # Union{Method, Nothing}
334
- while ! (infstate === nothing )
335
- infstate = infstate:: InferenceState
333
+
334
+ function matches_sv (parent:: InferenceState )
335
+ parent_method2 = parent. src. method_for_inference_limit_heuristics # limit only if user token match
336
+ parent_method2 isa Method || (parent_method2 = nothing ) # Union{Method, Nothing}
337
+ return parent. linfo. def === sv. linfo. def && sv_method2 === parent_method2
338
+ end
339
+
340
+ function edge_matches_sv (frame:: InferenceState )
341
+ inf_method2 = frame. src. method_for_inference_limit_heuristics # limit only if user token match
342
+ inf_method2 isa Method || (inf_method2 = nothing ) # Union{Method, Nothing}
343
+ if callee_method2 != = inf_method2
344
+ return false
345
+ end
346
+ if ! hardlimit
347
+ # if this is a soft limit,
348
+ # also inspect the parent of this edge,
349
+ # to see if they are the same Method as sv
350
+ # in which case we'll need to ensure it is convergent
351
+ # otherwise, we don't
352
+
353
+ # check in the cycle list first
354
+ # all items in here are mutual parents of all others
355
+ if ! _any (matches_sv, frame. callers_in_cycle)
356
+ let parent = frame. parent
357
+ parent != = nothing || return false
358
+ parent = parent:: InferenceState
359
+ (parent. cached || parent. parent != = nothing ) || return false
360
+ matches_sv (parent) || return false
361
+ end
362
+ end
363
+
364
+ # If the method defines a recursion relation, give it a chance
365
+ # to tell us that this recursion is actually ok.
366
+ if isdefined (method, :recursion_relation )
367
+ if Core. _apply_pure (method. recursion_relation, Any[method, callee_method2, sig, frame. linfo. specTypes])
368
+ return false
369
+ end
370
+ end
371
+ end
372
+ return true
373
+ end
374
+
375
+ for infstate in InfStackUnwind (sv)
336
376
if method === infstate. linfo. def
337
377
if infstate. linfo. specTypes == sig
338
378
# avoid widening when detecting self-recursion
@@ -349,60 +389,20 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
349
389
edgecycle = true
350
390
break
351
391
end
352
- inf_method2 = infstate. src. method_for_inference_limit_heuristics # limit only if user token match
353
- inf_method2 isa Method || (inf_method2 = nothing ) # Union{Method, Nothing}
354
- if topmost === nothing && method2 === inf_method2
355
- if hardlimit
356
- topmost = infstate
357
- edgecycle = true
358
- else
359
- # if this is a soft limit,
360
- # also inspect the parent of this edge,
361
- # to see if they are the same Method as sv
362
- # in which case we'll need to ensure it is convergent
363
- # otherwise, we don't
364
- for parent in infstate. callers_in_cycle
365
- # check in the cycle list first
366
- # all items in here are mutual parents of all others
367
- parent_method2 = parent. src. method_for_inference_limit_heuristics # limit only if user token match
368
- parent_method2 isa Method || (parent_method2 = nothing ) # Union{Method, Nothing}
369
- if parent. linfo. def === sv. linfo. def && sv_method2 === parent_method2
370
- topmost = infstate
371
- edgecycle = true
372
- break
373
- end
374
- end
375
- let parent = infstate. parent
376
- # then check the parent link
377
- if topmost === nothing && parent != = nothing
378
- parent = parent:: InferenceState
379
- parent_method2 = parent. src. method_for_inference_limit_heuristics # limit only if user token match
380
- parent_method2 isa Method || (parent_method2 = nothing ) # Union{Method, Nothing}
381
- if (parent. cached || parent. parent != = nothing ) && parent. linfo. def === sv. linfo. def && sv_method2 === parent_method2
382
- topmost = infstate
383
- edgecycle = true
384
- end
385
- end
386
- end
387
- end
392
+ topmost === nothing || continue
393
+ if edge_matches_sv (infstate)
394
+ topmost = infstate
395
+ edgecycle = true
388
396
end
389
397
end
390
- # iterate through the cycle before walking to the parent
391
- if cyclei < length (infstate. callers_in_cycle)
392
- cyclei += 1
393
- infstate = infstate. callers_in_cycle[cyclei]
394
- else
395
- cyclei = 0
396
- infstate = infstate. parent
397
- end
398
398
end
399
399
400
- if ! (topmost === nothing )
401
- topmost = topmost:: InferenceState
400
+ if topmost != = nothing
402
401
sigtuple = unwrap_unionall (sig):: DataType
403
402
msig = unwrap_unionall (method. sig):: DataType
404
403
spec_len = length (msig. parameters) + 1
405
404
ls = length (sigtuple. parameters)
405
+
406
406
if method === sv. linfo. def
407
407
# Under direct self-recursion, permit much greater use of reducers.
408
408
# here we assume that complexity(specTypes) :>= complexity(sig)
@@ -412,6 +412,13 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
412
412
else
413
413
comparison = method. sig
414
414
end
415
+
416
+ if isdefined (method, :recursion_relation )
417
+ # We don't recquire the recursion_relation to be transitive, so
418
+ # apply a hard limit
419
+ hardlimit = true
420
+ end
421
+
415
422
# see if the type is actually too big (relative to the caller), and limit it if required
416
423
newsig = limit_type_size (sig, comparison, hardlimit ? comparison : sv. linfo. specTypes, InferenceParams (interp). TUPLE_COMPLEXITY_LIMIT_DEPTH, spec_len)
417
424
@@ -427,6 +434,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
427
434
# (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases)
428
435
return Any, true , nothing
429
436
end
437
+ add_remark! (interp, sv, RECURSION_MSG)
430
438
topmost = topmost:: InferenceState
431
439
parentframe = topmost. parent
432
440
poison_callstack (sv, parentframe === nothing ? topmost : parentframe)
@@ -478,24 +486,13 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, @nosp
478
486
inf_cache = get_inference_cache (interp)
479
487
inf_result = cache_lookup (mi, argtypes, inf_cache)
480
488
if inf_result === nothing
481
- if edgecycle
482
- # if there might be a cycle, check to make sure we don't end up
483
- # calling ourselves here.
484
- infstate = sv
485
- cyclei = 0
486
- while ! (infstate === nothing )
487
- if match. method === infstate. linfo. def && any (infstate. result. overridden_by_const)
488
- add_remark! (interp, sv, " [constprop] Edge cycle encountered" )
489
- return Any, nothing
490
- end
491
- if cyclei < length (infstate. callers_in_cycle)
492
- cyclei += 1
493
- infstate = infstate. callers_in_cycle[cyclei]
494
- else
495
- cyclei = 0
496
- infstate = infstate. parent
497
- end
489
+ # if there might be a cycle, check to make sure we don't end up
490
+ # calling ourselves here.
491
+ if edgecycle && _any (InfStackUnwind (sv)) do infstate
492
+ return match. method === infstate. linfo. def && any (infstate. result. overridden_by_const)
498
493
end
494
+ add_remark! (interp, sv, " [constprop] Edge cycle encountered" )
495
+ return Any, nothing
499
496
end
500
497
inf_result = InferenceResult (mi, argtypes, va_override)
501
498
frame = InferenceState (inf_result, #= cache=# false , interp)
0 commit comments