-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
Remove inlining's dependence on method table lookups #39792
Conversation
@@ -981,8 +1003,16 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), | |||
ft = argtype_by_index(argtypes, 3) | |||
(itft === Bottom || ft === Bottom) && return CallMeta(Bottom, false) | |||
return abstract_apply(interp, itft, ft, argtype_tail(argtypes, 4), sv, max_methods) | |||
elseif f === invoke | |||
ft = widenconst(argtype_by_index(argtypes, 2)) |
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.
Should check argument count.
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.
argtype_by_index will check and also handle the Vararg
case correctly (similar to the apply case above).
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.
Nice reduction of code :)
new_info = info = nothing | ||
new_info = info = 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.
Why do we prefer false
over nothing
here ?
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.
nothing used to mean to try looking up the method match in inference, but we don't support that any more after this. Not that we actually ever inlined these cases.
@nanosoldier |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @christopher-dG |
Looks like a couple cases allocate more, a couple less, and no changes in speed |
I'm gonna take look to see if there's anything obvious wrong in the skipmissing cases, but I have seen those be a little all over the place in the past, since they're quite sensitive to the exact kinds of inlining that happen. |
Rather than relying on inlining to perform a method lookup.
Removes another dependence of inlining on the method table and has the nice side benefit of allowing external consumers like Cthulhu to reason about `invoke`.
In some cases we can statically compute the result of a pure call, but we refuse to inline it into the IR because it would be too big. In these cases, we'd still like to be able to do regular inlining, so augment MethodResult pure to forward the call info for such calls rather than relying on inlining to recompute the method match.
This is now no longer used. A use case for external consumers was to re-run another pass of inlining, but that is better addressed by a separate pass that simply sets the appropriate info.
@nanosoldier |
That turned out to be an actual bug that was masked by the previous fallback code: It didn't like the case where inference decided to union split, but only one of the cases actually had an applicable method. With that fixed, I think this'll be good to go, but I've triggered nanosoldier to reconfirm that test. |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @christopher-dG |
Confirmed fixed. Will merge once all CI comes back green. |
elseif isa(rty, Const) | ||
return CallMeta(Const(rty.val === false), nothing) | ||
return CallMeta(Const(rty.val === false), MethodResultPure()) |
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.
definitely not pure
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.
Why not?
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.
because rty
used to have actual provenance, but just threw it away?
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, unfolding context in github further, I see contextually we're guessing that this is the only legal answer from !==
?
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.
Nope, I'm still confused. For builtins that are in _PURE_BUILTINS
and return Const
we just implicitly treat the result as pure. That doesn't apply here, because !==
is not a builtin, but we are treating it as if it were one that simply had its result inverted. Why does that invalidate the purity of the call?
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.
jinx with your previous comment, but yes, !==
is special.
intersect!(et, WorldRange(invoke_data.min_valid, invoke_data.max_valid)) | ||
|
||
if !info.match.fully_covers | ||
# XXX: We could union split 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.
Doesn't seem like a bug (e.g. XXX
)? Also not true after this PR?
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.
Well, we don't have a standard notion of XXX
. In this case it's just here to let people know that'd it'd be ok to do, but I didn't implement it. If can use TODO
if you'd prefer. The union splitting that could be accomplished here is inlining the signature check, which isn't really a union split per-se, but the union split code kinda does as a side effect.
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.
True, XXX just looks like flag of bad code, where this is fine
types = rewrap_unionall(Tuple{ft, unwrap_unionall(types).parameters...}, types) | ||
nargtype = Tuple{ft, nargtype.parameters...} |
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.
argtype
isa DataType, but types
is not, so neither is nargtype
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, I see now we bail early in that case
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 is an nargtype isa DataType
check above. But regardless, this code just moved :)
When we switched to stmtinfo, we mostly stopped relying on doing method lookups during inlining.
However, there were a few leftover exceptions, in particular
This addresses these as well as a few corner cases that are arguable bugs. To address the first kind, we simply need a special purpose inliner that can understand how to properly inline the call despite not having method information. It turns out we already had this for a number of these cases, this PR just completes that. For invoke, we simply need to add a new statement info type and plumb the information through from inference.
Lastly, there is the use case of external optimization pipelines wanting to run multiple iterations of inlining after some more aggressive optimizations. However, I think that is better addressed by those external pipelines annotating the proper info object in a separate pass. One missing ingredient here is that we currently drop info objects after inlining in the optimizer, but I'm working on a separate PR that should address that as well.
I instrumented the fallback case and with this, they are dead code. There are a few cases that we might want to consider giving special purpose inliners in the future (e.g. typename and typejoin), but they weren't being inlined anyway because inference was never visiting them to populate any caches, so this is not a change of behavior.