-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
effects: make :terminates_globally functional on recursive functions
#44210
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
Conversation
2d8290e to
8e44096
Compare
| if edge === nothing | ||
| edgecycle = edgelimited = true | ||
| 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_bitss 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.