-
-
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: make :terminates_globally
functional on recursive functions
#44210
Conversation
2d8290e
to
8e44096
Compare
@@ -596,9 +596,18 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp | |||
end | |||
if edgecycle |
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.
There's also the code that deals with frame cycles in merge_call_chain!
, which will need similar treatment.
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.
Seems fine conceptually.
8e44096
to
651476b
Compare
base/compiler/typeinfer.jl
Outdated
tristate_merge!(parent, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) | ||
terminates_globally = is_effect_overrided(parent, :terminates_globally) | ||
frames = InferenceState[parent] | ||
while true | ||
add_cycle_backedge!(child, parent, parent.currpc) | ||
union_caller_cycle!(ancestor, child) | ||
tristate_merge!(child, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) | ||
terminates_globally |= is_effect_overrided(child, :terminates_globally) | ||
push!(frames, child) | ||
child = parent | ||
child === ancestor && break | ||
parent = child.parent::InferenceState | ||
end | ||
if !terminates_globally | ||
for frame in frames | ||
tristate_merge!(frame, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) | ||
end | ||
end | ||
return nothing |
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.
With this change, terminates
is encoded into ipo_effect_bits
of :terminates_globally
-functions no matter if they are involved with cycles.
I think it is valid to skip tainting frames in a cycle as long as any of them is annotated as :terminates_globally
, but I would like to check my understanding with your review, @Keno .
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.
you could write malicious functions that violate that, but I don't know if that is the intention of the annotation
f(x) = while x; g(); end
@assume_effects :terminates_globally g() = f(false)
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.
Ah, that's a good example, thanks.
So "termination" implied by :terminates_globally
annotation subsumes constant-prop', but merge_call_chain!
isn't a good place to handle it. Maybe we just want to just override edge effects in abstract_call_method
when the edge effect is specified by annotation, rather than trying hard to figure out the appropriate ipo_purity_bits
s for frames in a cycle.
As with loop, it's hard to prove termination of recursive call automatically. But with this commit we can manually specify termination at least. ```julia Base.@assume_effects :terminates_globally function recur_termination1(x) x == 1 && return 1 1 < x < 20 || throw("bad") return x * recur_termination1(x-1) end @test fully_eliminated() do recur_termination1(12) end Base.@assume_effects :terminates_globally function recur_termination2(x) x == 1 && return 1 1 < x < 20 || throw("bad") return _recur_termination2(x) end _recur_termination2(x) = x * recur_termination2(x-1) @test fully_eliminated() do recur_termination2(12) end ```
651476b
to
127a8ba
Compare
#44210) As with loop, it's hard to prove termination of recursive call automatically. But with this commit we can manually specify termination at least. ```julia Base.@assume_effects :terminates_globally function recur_termination1(x) x == 1 && return 1 1 < x < 20 || throw("bad") return x * recur_termination1(x-1) end @test fully_eliminated() do recur_termination1(12) end Base.@assume_effects :terminates_globally function recur_termination2(x) x == 1 && return 1 1 < x < 20 || throw("bad") return _recur_termination2(x) end _recur_termination2(x) = x * recur_termination2(x-1) @test fully_eliminated() do recur_termination2(12) end ```
JuliaLang#44210) As with loop, it's hard to prove termination of recursive call automatically. But with this commit we can manually specify termination at least. ```julia Base.@assume_effects :terminates_globally function recur_termination1(x) x == 1 && return 1 1 < x < 20 || throw("bad") return x * recur_termination1(x-1) end @test fully_eliminated() do recur_termination1(12) end Base.@assume_effects :terminates_globally function recur_termination2(x) x == 1 && return 1 1 < x < 20 || throw("bad") return _recur_termination2(x) end _recur_termination2(x) = x * recur_termination2(x-1) @test fully_eliminated() do recur_termination2(12) end ```
JuliaLang#44210) As with loop, it's hard to prove termination of recursive call automatically. But with this commit we can manually specify termination at least. ```julia Base.@assume_effects :terminates_globally function recur_termination1(x) x == 1 && return 1 1 < x < 20 || throw("bad") return x * recur_termination1(x-1) end @test fully_eliminated() do recur_termination1(12) end Base.@assume_effects :terminates_globally function recur_termination2(x) x == 1 && return 1 1 < x < 20 || throw("bad") return _recur_termination2(x) end _recur_termination2(x) = x * recur_termination2(x-1) @test fully_eliminated() do recur_termination2(12) end ```
As with loop, it's hard to prove termination of recursive call automatically.
But with this commit we can manually specify termination at least.