JIT: Devirtualization for generic virtual methods #112596
Labels
area-CodeGen-coreclr
CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Milestone
Generic virtual methods
In .NET we have generic virtual methods, which stand for virtual methods that have method instantiations. For example,
We made the method in the interface a generic method in order to avoid boxing, but unfortunately today all generic virtual methods go through a virtual function lookup followed by an indirect call:
This can make the code even slower than not making the method generic because we are not able to devirtualize any indirect call today.
Devirtualization story
Today we assert the base method desc must not have a method instantiation because with today's devirtualization, we will end up with a method desc with invalid method instantiations as they live in the base method desc. So we can create an associated method desc for it to put the instantiation information on the devirted method desc as well, after we find the exact method table:
Note that we need to handle unboxing stub, so for instance struct receivers, we need to force boxed entry point. And because the method itself is generic,
allowInstParam
should befalse
.Then with the devirted method desc, we can call it with
InstParam
directly without going through the virtual function pointer lookup. While there's a case where it can eventually end up with a canonical method table as the exact method table, in which case we need to bail.But if we take a deeper look:
we can find that, although we have all the necessary information, we spilled the
ldvirtftn
so that we lost those information when we do the indirect call, so we don't have the method desc we want when we do the devirtualization.And furthermore, even we have all the necessary information we need to devirtualize the call, the devirted method may be an instantiating stub that requires a runtime lookup, in this case we cannot use the instantiating stub from
WrappedMethodDesc
we created byFindOrCreateAssociatedMethodDesc
before as theInstParam
, so we still need to put the runtime lookup node as anInstParam
arg.The solution to this is to not spill it early, so that we can end up trees like
Then we will have all the necessary information for devirtualization. After devirtualization, we can push the necessary method
InstParam
to the call. In the above case we don't need a methodInstParam
so it will end upfor cases where we need a method
InstParam
, it may end up:or when a runtime lookup is required (a real-world example):
we can devirt it into
So far we managed to devirtualize generic virtual methods, then we can unblock the inlining, even for cases where runtime lookup is needed for method inst.
However, the JIT backend doesn't handle well when the call address is a
CALL
in an indirect call, that is, if we failed to devirtualize a generic virtual call, we will end upwhere the backend is not handling it well.
As a temporary workaround, we can split the call so that the
ldvirtftn
will first be executed and stored into a local, and the call address can be replaced with the local. But the real fix here is to fix the backend handling so that we don't need to split it at all.The prototype has done in #112353, and it shows many interesting optimization opportunities across logging, json parsing, LINQ/PLINQ, collections, hosting, dependency injection that being extensively adopted by all kinds of apps today and etc., see MihuBot/runtime-utils#1004 (code size regression are due to more inlining). And this work is also a prerequisites of enabling devirtualizing delegates that require a closure (capture locals).
NativeAOT uses a fat pointer for this so it need to be handled separately.
Taking the above code as an example,
before:
after:
Plans
ldvirtftn
CALL
as call address of an indirect call in the backend#112353 covered 1~5, while 2 was handled by the workaround I mentioned before.
cc @dotnet/jit-contrib
cc @jkotas @MichalStrehovsky for review and suggestions on the VM part
The text was updated successfully, but these errors were encountered: