-
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
RyuJIT should not inline throw only methods #4381
Comments
Why would you want such a method anyway? Usually throw only methods are done because you have complex code to create *Hibernating Rhinos Ltd * Oren Eini* l CEO l *Mobile: + 972-52-548-6969 Office: +972-4-622-7811 *l *Fax: +972-153-4-622-7811 On Wed, Jul 22, 2015 at 8:28 AM, mikedn [email protected] wrote:
|
Exactly because I didn't expect such a method to be inlined and didn't want to bloat the caller with useless code. I discovered this in the following code: unsafe struct ByteStream {
...
private byte* Read(uint count) {
uint p = rva;
uint n = p + count;
if (n > rvaEnd)
ThrowInvalidOperationException();
rva = n;
return basePtr + p;
}
public uint ReadByte() {
return *Read(1);
}
private void ThrowInvalidOperationException() {
throw new InvalidOperationException();
} While looking at the generated code I discovered that But more generally this is useful when doing argument validation and the example I posted originally is meant to show that. Of course, there's a trivial workaround, add the |
Speaking of BCL, the size of crossgened mscorlib is 18,435,584 bytes. If the fix is applied the size is 18,320,384 bytes, that's some 100 kbytes less. |
We've talked about doing this; it would be a good improvement. |
@BruceForstall Does relying on the lack of return basic blocks sound like a good approach for fixing this? It seems to work fine for cases like the one I posted. It won't work if there are return blocks that are later eliminated as dead code but I suspect that's difficult to fix given the way inlining decisions are taken. |
That sounds like a good approach. It should handle the most likely case of simple "throw" function wrappers. |
I used to wrap throws in throw only methods because I thought throw prevents method inlining and bloats method body size. |
@BruceForstall Thanks, I managed to convince crossgen to dump the disassembly for mscorlib and it looks pretty good. Initially I was a bit confused by some associations between jumps and BBJ_RETURN until I realized that they're about the IL jmp instruction which is very different from the x86 jmp instruction. There is one small negative side effect of not inlining such methods - the compiler doesn't know that they never return and in some cases it moves registers around to avoid having them killed by the call. I suppose it would be nice to avoid this if possible. |
Maybe @AndyAyersMS would be interested in looking at this. Not necessarily at the inlining decision itself which shouldn't be a problem to fix but at propagating the "no return" information from the inlinee compiler to the inliner. No return methods should be |
Would be nice to have an attribute with which we could mark methods. It would require verification that a marked method either throws or calls FailFast. @mikedn Is that what you mean with 'no return methods'? |
Recent PR on the subject. |
Isn't that what [MethodImpl(MethodOptions.NoInlining)] for? *Hibernating Rhinos Ltd * Oren Eini* l CEO l *Mobile: + 972-52-548-6969 Office: +972-4-622-7811 *l *Fax: +972-153-4-622-7811 On Sat, Feb 6, 2016 at 9:54 PM, OmariO [email protected] wrote:
|
Yes, I intend to look at inlining in exceptional contexts. I've seen a number of examples where I think the jit inlines too much along paths leading to exceptions. Declaring or deducing that a method is @ayende |
Moving to the end is not very useful. The real advantage of having "no return" information is avoiding unnecessary register moves/saves. See the following example (adapted from class Program {
class List<T> {
private T[] array = new T[1];
private int count = 1;
public T this[int index] {
get {
if (index >= count) ThrowArgumentOutOfRangeException();
return array[index];
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowArgumentOutOfRangeException() {
throw new ArgumentOutOfRangeException("index");
}
[MethodImpl(MethodImplOptions.NoInlining)]
static int Test(List<int> list, int index) {
return list[index];
}
static int Main() {
return Test(new List<int>(), 42);
}
} Code generated for Test:
Because the JIT compiler thinks that As you have seen in a corefxlab PR keeping the
I had to dump the disassembly for But keeping the throw out of the helper means that the throw code is now slightly larger. Without JIT support it's not possible to get ideal code for argument validation. And argument validation is quite common in .NET... |
@mikedn btw, is servicing updates a concern in CoreCLR? |
@omario what does it mean, service updates? Service packs? *Hibernating Rhinos Ltd * Oren Eini* l CEO l *Mobile: + 972-52-548-6969 Office: +972-4-622-7811 *l *Fax: +972-153-4-622-7811 On Sun, Feb 7, 2016 at 12:30 PM, OmariO [email protected] wrote:
|
@ayende If understand it correctly because of them JIT (which does NGEN as well) is restricted in some optimizations it can do. For example, when a sealed method becomes unsealed or when unconditionally throwing method start throwing conditionally. |
Please do NOT remove throw only methods inlining because F# already use it in NoDynamicInvocation attribute implementations. According to the language spec (3.1) NoDynamicInvocation means: When applied to an inline function or member definition, replaces the generated code with a stub that throws an exception at runtime. This attribute is used to replace the default generated implementation of unverifiable inline members with a verifiable stub. This attribute should be used only in F# assemblies. |
@zpodlovics That's not quite accurate - the F# compiler does it's own inlining on the actual implementation of these pseudo-methods which are known only to the compiler - so it's still correct for RyuJIT to avoid inlining them |
When we consider such a method for inlining we should: |
@briansull I experimented with this when reporting the issue and rejecting inlining seemed easy enough. I suppose marking the block as RarelyRun should be trivial but that's not enough to get best CQ. The BB kind should also be changed to something that works like |
@mikedn, speaking of Register save/restore, does CoreCLR have support for leaf methods? If it's a todo, this could fall under it (noReturn or alwaysThrows attribute means reg save/restore could be elided and ThrowArgumentOutOfRangeException() block becomes an exit). |
@choikwa I'm not sure what you mean by "support for leaf methods" and how such support relates to this issue. Usually a "leaf method" is a method that does not call other methods, not a method that does not return. |
Nevermind, what I was thinking of would apply to unmanaged code. Certain leaf methods can be scanned by caller to skip save/restoring if it can determine that callee won't use them. |
Fixed by dotnet/coreclr#6103 |
The
ThrowArgsNull
method in the following example gets inlined even though there's no reason to do so, throwing exceptions isn't fast to begin with and such codepaths aren't supposed to be common anyway. The inlined code size is significantly larger than the call site (in this example 57 bytes vs 5 bytes).More generally, there's probably no reason to inline any method that doesn't contain at least one BBJ_RETURN block.
The text was updated successfully, but these errors were encountered: