Skip to content
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

Merged
merged 5 commits into from
Mar 2, 2021
Merged

Conversation

Keno
Copy link
Member

@Keno Keno commented Feb 23, 2021

When we switched to stmtinfo, we mostly stopped relying on doing method lookups during inlining.
However, there were a few leftover exceptions, in particular

  1. Special cases like the TypeVar constructor, which had special support in inference and thus did not look into the methods that are supposed to be inlined.
  2. Inlining support for :invoke

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.

@@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should check argument count.

Copy link
Member Author

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).

Copy link
Member

@aviatesk aviatesk left a 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
Copy link
Member

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 ?

Copy link
Member Author

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.

@Keno
Copy link
Member Author

Keno commented Feb 27, 2021

@nanosoldier runbenchmarks(ALL, vs = ":master")

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @christopher-dG

@vtjnash
Copy link
Member

vtjnash commented Feb 27, 2021

Looks like a couple cases allocate more, a couple less, and no changes in speed

@Keno
Copy link
Member Author

Keno commented Mar 1, 2021

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.

Keno added 5 commits March 1, 2021 13:22
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.
@Keno Keno force-pushed the kf/inlinemtable branch from 9ee85d4 to f166b20 Compare March 1, 2021 19:43
@Keno
Copy link
Member Author

Keno commented Mar 1, 2021

@nanosoldier runbenchmarks("union", vs = ":master")

@Keno
Copy link
Member Author

Keno commented Mar 1, 2021

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.

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @christopher-dG

@Keno
Copy link
Member Author

Keno commented Mar 1, 2021

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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely not pure

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not?

Copy link
Member

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?

Copy link
Member

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 !==?

Copy link
Member Author

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?

Copy link
Member Author

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
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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

Comment on lines +1084 to +1085
types = rewrap_unionall(Tuple{ft, unwrap_unionall(types).parameters...}, types)
nargtype = Tuple{ft, nargtype.parameters...}
Copy link
Member

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

Copy link
Member

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

Copy link
Member Author

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 :)

@Keno Keno merged commit 3ff44ea into master Mar 2, 2021
@Keno Keno deleted the kf/inlinemtable branch March 2, 2021 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants