-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
effects: Do not over-taint :terminates in abstract cycle #49119
Conversation
fb38ae9
to
ca85073
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this premise is true. This proves the absences of recursion in the type graph, but that only implies that the type information is not required to be propagated, not that their is a cycle-free call graph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, it seems the existing code is marking this property much too late, as the cycle detection occurs (and is discarded) as early as line 564 in this file.
The cycle-detection-with-unused-result on 564 is separate and taints all effects (#48981) |
I think the general argument goes something like this:
Can you think of another case where inference does terminate, but none of these four conditions are true? |
|
Right, but presumably as soon as one callsite in a cycle of methods is marked as non-terminating, that should just propagate by the usual rules of effect-propagation, no? |
Ah, right. Either the call should return and be finished, or it should report a cycle. And in particular, we can even have cycles in the inference types graph, without have cycles in the call graph (because of the behavior of the return_type function) |
Yes |
I agree with deleting the too conservative tainting mechanism within typeinf.jl while keeping the tainting within
|
We already infer non-termination as part of the conservative effects we assume at the point in the call-graph that recursion is detected. As a result, it should be sound to allow this to propagate through the Effects system naturally rather than eagerly marking our callers as non-terminating. Doing this is important to avoid tainting non-termination from purely abstract cycles, where inference is forced to analyze methods that do not correspond to real calls at runtime, such as `Base._return_type`. Resolves JuliaLang#48983.
Agreed! Just trying to put together a test case now for the gap in my original logic.
If I understand correctly, the effects observed by Since we're already returning |
This extra tainting makes it difficult for me to come up with a test case that would evade it: julia/base/compiler/abstractinterpretation.jl Lines 562 to 569 in 124abaa
but either way I'm fine with doing this tainting in abstract_call_method |
Yes, the main reason why I'm suggesting it is because it's generally safe to be conservative. I intended to taint the parent frame if !(is_effect_overridden(sv, :terminates_globally) || # this frame is known to terminate
is_effect_overridden(match.method, :terminates_globally)) # this edge is known to terminate
# `abstract_call_method` may have returned this callee's effects with their `:terminates`
# tainted already, but let's make sure to taint it here conservatively
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; terminates=true))
end This would be safe because while we usually expect our analysis to be monotonic, i.e. |
Yeah, I see what you're saying. At the same time, my devil's advocate position would be that conservatively tainting in anticipation of a bug elsewhere in the code just makes that bug harder to find later, especially since the conservative solution here would be an incomplete band-aid. If If that reasoning stands, my preference would be to leave out the extra tainting in _with_const_args, but I'm happy to defer to others if there's a strong intuition from more experienced folks than myself |
Yeah, you're right. I remember we encountered some cases when |
This is correct. The result of inference between these 2 calls is the intersection of their results. Thus if either can show that it terminates, then it terminates, even if the other one couldn't prove that. |
…9119) We already infer non-termination as part of the conservative effects we assume at the point in the call-graph that recursion is detected. As a result, it should be sound to allow this to propagate through the Effects system naturally rather than eagerly marking our callers as non-terminating. Doing this is important to avoid tainting non-termination from purely abstract cycles, where inference is forced to analyze methods that do not correspond to real calls at runtime, such as `Base._return_type`. Resolves JuliaLang#48983.
The idea here is to propagate non-termination-by-recursion through the Effects system naturally rather than eagerly marking all callers in a cycle as non-terminating.
Doing this is important to avoid tainting non-termination from purely abstract cycles, where inference is forced to analyze methods that do not correspond to real calls at runtime, such as
_return_type
Resolves #48983.