@@ -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,22 @@ 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
+ for infstate in InfStackUnwind (sv)
336
341
if method === infstate. linfo. def
337
342
if infstate. linfo. specTypes == sig
338
343
# avoid widening when detecting self-recursion
@@ -349,52 +354,33 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
349
354
edgecycle = true
350
355
break
351
356
end
357
+ topmost === nothing || continue
352
358
inf_method2 = infstate. src. method_for_inference_limit_heuristics # limit only if user token match
353
359
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
360
+ if callee_method2 === inf_method2
361
+ if ! hardlimit
359
362
# if this is a soft limit,
360
363
# also inspect the parent of this edge,
361
364
# to see if they are the same Method as sv
362
365
# in which case we'll need to ensure it is convergent
363
366
# 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
367
+
368
+ # check in the cycle list first
369
+ # all items in here are mutual parents of all others
370
+ if ! _any (matches_sv, infstate. callers_in_cycle)
371
+ let parent = infstate. parent
372
+ parent != = nothing || continue
378
373
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
374
+ (parent. cached || parent. parent != = nothing ) || continue
375
+ matches_sv (parent) || continue
385
376
end
386
377
end
387
378
end
379
+
380
+ topmost = infstate
381
+ edgecycle = true
388
382
end
389
383
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
384
end
399
385
400
386
if ! (topmost === nothing )
@@ -427,6 +413,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
427
413
# (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases)
428
414
return Any, true , nothing
429
415
end
416
+ add_remark! (interp, sv, RECURSION_MSG)
430
417
topmost = topmost:: InferenceState
431
418
parentframe = topmost. parent
432
419
poison_callstack (sv, parentframe === nothing ? topmost : parentframe)
@@ -478,24 +465,13 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, @nosp
478
465
inf_cache = get_inference_cache (interp)
479
466
inf_result = cache_lookup (mi, argtypes, inf_cache)
480
467
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
468
+ # if there might be a cycle, check to make sure we don't end up
469
+ # calling ourselves here.
470
+ if edgecycle && _any (InfStackUnwind (sv)) do infstate
471
+ return match. method === infstate. linfo. def && any (infstate. result. overridden_by_const)
498
472
end
473
+ add_remark! (interp, sv, " [constprop] Edge cycle encountered" )
474
+ return Any, nothing
499
475
end
500
476
inf_result = InferenceResult (mi, argtypes, va_override)
501
477
frame = InferenceState (inf_result, #= cache=# false , interp)
0 commit comments