-
-
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
inference: parameterize some of hard-coded inference logic #39439
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -212,3 +212,16 @@ may_compress(ni::NativeInterpreter) = true | |
may_discard_trees(ni::NativeInterpreter) = true | ||
|
||
method_table(ai::AbstractInterpreter) = InternalMethodTable(get_world_counter(ai)) | ||
|
||
# define inference bail out logic | ||
# `NativeInterpreter` bails out from inference when | ||
# - a lattice element grows up to `Any` (inter-procedural call, abstract apply) | ||
# - a lattice element gets down to `Bottom` (statement inference, local frame inference) | ||
# - inferring non-concrete toplevel call sites | ||
bail_out_call(interp::AbstractInterpreter, @nospecialize(t), sv) = t === Any | ||
bail_out_apply(interp::AbstractInterpreter, @nospecialize(t), sv) = t === Any | ||
bail_out_statement(interp::AbstractInterpreter, @nospecialize(t), sv) = t === Bottom | ||
bail_out_local(interp::AbstractInterpreter, @nospecialize(t), sv) = t === Bottom | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the difference between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
function bail_out_toplevel_call(interp::AbstractInterpreter, @nospecialize(sig), sv) | ||
return isa(sv.linfo.def, Module) && !isdispatchtuple(sig) | ||
end |
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.
Upon further reflection, I'm not sure about this one. The
return Bottom
here kinda implies that the only thing this could possibly do is to check for a literalBottom
, no? Maybe it's a similar situation where the bail check needs to be separate from the check for Bottom.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 still think this could be useful for code analysis (but not in terms of optimization).
Say if we analyze this kind of code below:
I believe it's better to report two errors in this case (i.e.
NoMethodError(*, (Vector{Int}, Missing))
,UndefVarError(:sinn)
)).With the
bail_out_statement
andbail_out_local
interface, we can do something like below:and keep analysis going and collect as much errors as possible even when we know the actual execution terminates earlier.
I agree with this interface would be weird from optimization perspective, though.
/cc @vtjnash because you added commit to revert these interfaces in #39606. Could I hear your ideas on this ?
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.
Hmm, I thought you wanted to use these to early-out in useless analysis rather than to continue into unreachable code-paths. I think doing the latter can be pretty tricky.
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.
Those aren't the same conditions though. If you did want to bail early, it should be using the
Union{}
lattice element (otherwise we may exit before the lattice has finished converging). If you want to bail late, that decision doesn't happen here either (if later tfuncs are correct, they should also return Union{} in this case, but it would be a wild mess add this check everywhere). That decision happens intypeinf_local
, where we decide to stop forwarding information to the next statement rather than, say, attempting to setchanges = fill!(similar(changes), VarState(Union{}, true))
so that we reach each subsequent statement (Though most will also error. Since this is dead/unreachable code, that is what we want, to avoid poisoning the actual inference results at the next phi node join point.)I agree you could replace Union{} with Any (not here, but when they are accessed from the Slot), but that might quite quickly confuse the convergence algorithm and drive many more function towards being uninferrable. To avoid that, I think you'd realistically need to run inference repeatedly, "fixing" one error at a time from the outside driver.
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 general, I strongly dislike parametrization just for the sake of parameterization, since it bloats the compiler and makes it harder to follow the logical flow.
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.
Thanks for your responses.
Fair. As you pointed out my idea will be really complicated otherwise it will ruin inference results and convergence.
Even when I try that in the future, I think I will end up overloading the entire body of
typeinf_local
anyway, so honestly I'm not too stick withbail_out_local/statement
interface and I'm okay with #39606.Yeah, that's what I was worried about. Are you okay with
bail_out_call
/bail_out_apply
? I'm fairly sure they won't destroy the convergence nor inference results anyway, but still might look weird in term of the implementation ofNativeInterpreter
. Still they are very useful for the implementation ofJETInterpreter
.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.
Those seem less bad, since it is probably just a performance optimization. There's also possibly not really any other correct behavior there to put in the overload either? We already computed the final call graph (info) and return type (Any), and no further analysis work should alter those. Thus also where we see how the optimizer is designed to run as a separated pass, and consumes both of those bits of information later and avoids being dependent upon the precise way it arrived at the answer.