You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
; Assembly listing for method Program:<<Main>$>g__Test|0_0[int](int) (Tier1); Emitting BLENDED_CODE for X64 with AVX - Windows; Tier1 code; optimized code; optimized using Dynamic PGO; rbp based frame; fully interruptible; with Dynamic PGO: edge weights are valid, and fgCalledCount is 1.446707e+07; BEGIN METHOD Program:<<Main>$>g__Test|0_0[int](int)G_M000_IG01: ;; offset=0000Hpushrbpsubrsp,48learbp,[rsp+30H]mov qword ptr [rbp-10H],rspG_M000_IG02: ;; offset=000EHaddrsp,48poprbpretG_M000_IG03: ;; offset=0014Hint3G_M000_IG04: ;; offset=0015Hpushrbpsubrsp,48movrbp, qword ptr [rcx+20H]mov qword ptr [rsp+20H],rbplearbp,[rbp+30H]G_M000_IG05: ;; offset=0027Hmovrdx, gword ptr [rbp-08H]movrcx,0x7FF93AB130D0call[CORINFO_HELP_ISINSTANCEOFINTERFACE]testrax,raxje SHORT G_M000_IG06movrcx,raxmovr11,0x7FF93A940098call[r11]System.IDisposable:Dispose():thisG_M000_IG06: ;; offset=0050HnopG_M000_IG07: ;; offset=0051Haddrsp,48poprbpret ; END METHOD Program:<<Main>$>g__Test|0_0[int](int); Total bytes of code 87
when in reality it should just be more like:
; Assembly listing for method Program:<<Main>$>g__Test|0_0[int](int) (Tier1); Emitting BLENDED_CODE for X64 with AVX - Windows; Tier1 code; optimized code; rsp based frame; partially interruptible; No PGO data; BEGIN METHOD Program:<<Main>$>g__Test|0_0[int](int)G_M000_IG01: ;; offset=0000Hret ; END METHOD Program:<<Main>$>g__Test|0_0[int](int); Total bytes of code 1
Haha, interesting, so your codegen looks fine - see, it just pushes something to stack (generic context?) and then just exits, but JIT wasn't able to delete a big chunk of unreachable code - presumably, because of exception handling from forerach - need to take a look
Haha, interesting, so your codegen looks fine - see, it just pushes something to stack (generic context?) and then just exits
But why does it even need to do that? It should be no different from an empty method body, no?
I guess it thinks it's not an leaf method due to calls in the unreachable blocks , hence, it needs to setup a frame.
Also, removing the NoInlining on Test, it still doesn't get inlined even though it should be a nop equivalent to an empty method.
My bet that it's pretty hard for inliner to guess here - the main issue that our boxing optimizations are pretty simple pattern matchers, like BOX+ISINST+BRTRUE -> fold. It's not the case for e.g. if (obj is Foo foo) syntax because it produces BOX+ISINST+STLOC+LDLOC+BTRUE that our matcher no longer can optimize early, I'll check why the blocks were not removed in your case
the main issue that our boxing optimizations are pretty simple pattern matchers, like BOX+ISINST+BRTRUE -> fold. It's not the case for e.g. if (obj is Foo foo) syntax because it produces BOX+ISINST+STLOC+LDLOC+BTRUE that our matcher no longer can optimize early
Ugh. So, yeah, if I change the repro to instead be:
then it generates the desired (albeit still not inlined):
; Assembly listing for method Program:<<Main>$>g__Test|0_0[int](int) (Tier1); Emitting BLENDED_CODE for X64 with AVX - Windows; Tier1 code; optimized code; rsp based frame; partially interruptible; No PGO data; BEGIN METHOD Program:<<Main>$>g__Test|0_0[int](int)G_M000_IG01: ;; offset=0000HG_M000_IG02: ;; offset=0000Hret ; END METHOD Program:<<Main>$>g__Test|0_0[int](int); Total bytes of code 1
It's really unfortunate that the C# language, our style rules, analyzers, IDE suggestions, etc. all push us and others to use the is IEnumerable enumerable variant, but the JIT is unable to handle that as well. I'd thought this was only a deficiency around boxing in a case like:
Tvalue=...;if(valueisIFoofoo)foo.Bar();
and instead needing to write that as:
Tvalue=...;if(valueisIFoo)((IFoo)foo).Bar();
to avoid the boxing, but it now sounds like the problem is much more extensive than that.
So, looks like it's a general issue with JIT today (#82335) that it afraids to remove unreachable blocks with EH, even in this simple example:
(in some cases, jit can do that, though, e.g. remove empty finally-block).
to avoid the boxing, but it now sounds like the problem is much more extensive than that.
Yeah, we're aware of it and have an idea to fix it (it's the same old "multi-use boxing" problem) but it's just not trivial from my understanding (e.g. escape analysis) - @AndyAyersMS@markples might know more about it
Yeah, we're aware of it and have an idea to fix it (it's the same old "multi-use boxing" problem) but it's just not trivial from my understanding (e.g. escape analysis) - @AndyAyersMS@markples might know more about it
We had ambitions of fixing (at least some) cases of these multi-use boxes in .NET 8 but didn't get to it ... so unfortunately it is going to have to wait.
The removal of unreachable EH might be easier to address, but not sure if it would fit into the remaining .NET 8 time either. Seems like moderate risk for (what currently feels like) small reward. Might not be too hard to assess the impact more quantitatively.
Activity
stephentoub commentedon Jul 17, 2023
@EgorBo, if I change your example slightly to enumerate the enumerable in the if block:
I get this for the "optimized" implementation:
when in reality it should just be more like:
What's going on there?
EgorBo commentedon Jul 17, 2023
Haha, interesting, so your codegen looks fine - see, it just pushes something to stack (generic context?) and then just exits, but JIT wasn't able to delete a big chunk of unreachable code - presumably, because of exception handling from forerach - need to take a look
stephentoub commentedon Jul 17, 2023
But why does it even need to do that? It should be no different from an empty method body, no?
stephentoub commentedon Jul 17, 2023
Also, removing the NoInlining on
Test<T>, it still doesn't get inlined even though it should be a nop equivalent to an empty method.EgorBo commentedon Jul 17, 2023
I guess it thinks it's not an leaf method due to calls in the unreachable blocks , hence, it needs to setup a frame.
My bet that it's pretty hard for inliner to guess here - the main issue that our boxing optimizations are pretty simple pattern matchers, like
BOX+ISINST+BRTRUE-> fold. It's not the case for e.g.if (obj is Foo foo)syntax because it producesBOX+ISINST+STLOC+LDLOC+BTRUEthat our matcher no longer can optimize early, I'll check why the blocks were not removed in your casestephentoub commentedon Jul 17, 2023
Ugh. So, yeah, if I change the repro to instead be:
then it generates the desired (albeit still not inlined):
It's really unfortunate that the C# language, our style rules, analyzers, IDE suggestions, etc. all push us and others to use the
is IEnumerable enumerablevariant, but the JIT is unable to handle that as well. I'd thought this was only a deficiency around boxing in a case like:and instead needing to write that as:
to avoid the boxing, but it now sounds like the problem is much more extensive than that.
EgorBo commentedon Jul 17, 2023
So, looks like it's a general issue with JIT today (#82335) that it afraids to remove unreachable blocks with EH, even in this simple example:
(in some cases, jit can do that, though, e.g. remove empty finally-block).
Yeah, we're aware of it and have an idea to fix it (it's the same old "multi-use boxing" problem) but it's just not trivial from my understanding (e.g. escape analysis) - @AndyAyersMS @markples might know more about it
AndyAyersMS commentedon Jul 17, 2023
We had ambitions of fixing (at least some) cases of these multi-use boxes in .NET 8 but didn't get to it ... so unfortunately it is going to have to wait.
The removal of unreachable EH might be easier to address, but not sure if it would fit into the remaining .NET 8 time either. Seems like moderate risk for (what currently feels like) small reward. Might not be too hard to assess the impact more quantitatively.