-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
optimization of indirect calls #44610
Comments
Since ASP.NET Core 6, delegates and lambdas are being heavily used in the new minimal-api design which uses delegates as endpoint handlers here and there. I believe if JIT can optimize delegates to direct calls, a huge performance improvement will be introduced. |
Allow method handle histograms in .mibc files and in the PGO text format. Contributes to dotnet#44610.
Allow method handle histograms in .mibc files and in the PGO text format. Contributes to #44610.
I'm working on this currently but I have a bit of an issue regarding invalid code handling with |
In general, runtime methods like You would need to create your own implementation of static readonly void* pfn = ....;
static readonly PfnKind pfnKind = ....;
static void DoStuff()
{
switch (pfnKind)
{
// pfn can be inlined only for the case that matches the actual pfn signature
case MethodWithOneArgument: ((delegate* <int, void>)pfn)(arg1); break;
case MethodWithTwoArguments: ((delegate* <int, void>)pfn)(arg1, arg2); break;
case BogusMethod: return;
}
} |
The issue is that it's not fully clear to me what's the proper way to validate those, do you have any hints with that?
Yeah I do realise that, it's much less of an issue though since I have the signature of the calli and target method already, just have to compare those. |
Some examples:
|
Also, you will need to check whether the loader allocator of the resolved function pointer is in the dependency closure of the method being compiled. It is necessary to avoid introducing a dependency of non-collectible code on collectible code. |
I've tried to do so by checking whether the |
These data structures were not designed to allow validation of entry points. I think they would require redesign to allow it. It does not look easy. |
Is creating a global Or maybe we could just decide that storing invalid pointers to managed methods in
and since having a pointer that points to JIT code range but isn't valid already requires some weird invalid code, since null or pointers to unmanaged that are casted to managed will be fine here. |
It does not sound very pay for play. This is niche optimization, vast majority of existing function pointer uses are not going to benefit from it, but they would still need to pay for it.
It would be a breaking change. Function pointers were invented first and foremost for managed C++. Does C++ define any of this as UB?
It is not that unusual as you may think. There is a defense-in-depth school of thought that encourages storing sensitive pointers (that includes code pointers) in statics in encrypted form. Here is a random code example that found after searching for 10 seconds on github: https://github.com/rituraj0/Old-Codes/blob/c2bc55bd94da84d404e6ed3be92b81ffcfbaa74f/Trace.cpp#L45 |
It's implementation defined:
There's also:
This wouldn't be an issue here since such obfuscation decodes such pointers on use, this issue only manifests when directly calling an invalid pointer, it just moves the AV from call time to JIT time. (This is an issue ONLY with code that'd AV if the call actually happend, now the call doesn't need to happen, just to be possible). |
And I guess being able to check whether a |
I've checked GCC, Clang and MSVC, they all seem to detect this, inline the valid case and leave the invalid one as is so that it AVs on call (same as dotnet right now). |
Hmm, the only solution I could think of that'd have small runtime overhead would be introducing a hidden |
This would be between impossible and very complicated. #44610 (comment) should be a lot easier than this. |
Imports indirect calls as direct ones when the target method is known. Only handles addresses from ldftn as the VM has no way to verify pointers from static readonly fields and crashes on invalid ones. The helpers currently contain a small dead path that I'll soon use when extending the code to also handle delegates. Opening as a draft so that the code can be reviewed while I finish the tests. Fixes dotnet#44610
With the advent of function pointers we will now see an increased number of indirect call cases that the jit can optimize to direct calls. This requires a transformation similar to the one we do for devirtualization. There may be additional wrinkles, say if someone takes the address of an intrinsic method we would want intrinsic recognition to kick in too. So in the importer perhaps this needs to sit just upstream from
impImportCall
.Overall this should be structured in a similar way to devirtualization -- opportunistically transforming calls during importation to enable subsequent inlining, and then perhaps retry later after inlining to at least remove overhead. Would be nice to be able to change over in the optimizer too, but that may prove challenging as we start to bake in many details in morph.
In principle we could do something similar with locally created delegates but there are a number of missing pieces that prevent us from seeing through from delegate creation to invocation.
produces
Not sure of the priority of this just yet, I am looking at some apps that use calli fairly heavily and need to better understand how many of these can be optimized. So marking as future for now.
This optimization will also intersect with guarded devirtualization / PGO, provided we can profile indirect targets and see biased distributions.
category:cq
theme:devirtualization
skill-level:expert
cost:medium
The text was updated successfully, but these errors were encountered: