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

JIT does not elide empty try/catch blocks #107191

Closed
colejohnson66 opened this issue Aug 30, 2024 · 3 comments · Fixed by #110273
Closed

JIT does not elide empty try/catch blocks #107191

colejohnson66 opened this issue Aug 30, 2024 · 3 comments · Fixed by #110273
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI in-pr There is an active PR which will close this issue when it is merged tenet-performance Performance related issue
Milestone

Comments

@colejohnson66
Copy link

colejohnson66 commented Aug 30, 2024

Description

If we want to format some runtime values for a Debug.WriteLine invocation, it's possible for the formatting method to throw. So, I figured I'd wrap a try/catch around the Debug.WriteLine call. However, the JIT does not elide the block in release mode.

https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABABgAJiBGAOgBEBLbAcwDsJcMGxcBuAWABQZSrQBKAV1Zd8MGgGEI+AA4MANjCgBlTQDduMPkJHUaAFRgIMAwcYDMlAEzl5avLiEBvIeV/kA2gCyMBgAFhAAJgCSKmoAFMFhkTHKagDyylwQrLg0AILMzLC4uAy6MBkyDABe2FmsAJQAuj5+xA7EKOSJ4RFxwACeGDD+TeQRddgNrb7egn4L5BhQAzOLc4ub5HQwwBLMNADqUAzDADIMrDBxAKKskBGXBwCqZgBiABw0AOIhWstPOITDBTBo2LbkAC+awWYDqYFCML8Gwhfh2ewOx1OMAuVziACJgNgImoBuQAGbQfB1YYRcaTfFgpG+aHzPysyFAA===

C# / IL / JIT ASM
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;

public class Class
{
    [MethodImpl(MethodImplOptions.AggressiveOptimization)]
    public void Method(byte[] data)
    {
        try
        {
            Debug.WriteLine(Encoding.UTF8.GetString(data));
        }
        catch
        {
            Debug.WriteLine("badly formatted data");
        }
    }
}
.method public hidebysig 
    instance void Method (
        uint8[] data
    ) cil managed flag(0200) 
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
        01 00 01 00 00
    )
    // Method begins at RVA 0x2050
    // Code size 6 (0x6)
    .maxstack 1

    .try
    {
        IL_0000: leave.s IL_0005
    } // end .try
    catch [System.Runtime]System.Object
    {
        IL_0002: pop
        IL_0003: leave.s IL_0005
    } // end handler

    IL_0005: ret
} // end of method Class::Method
; Core CLR 8.0.724.31311 on x64

Class..ctor()
    L0000: ret

Class.Method(Byte[])
    L0000: push rbp
    L0001: sub rsp, 0x10
    L0005: lea rbp, [rsp+0x10]
    L000a: mov [rbp-0x10], rsp
    L000e: add rsp, 0x10
    L0012: pop rbp
    L0013: ret
    L0014: push rbp
    L0015: sub rsp, 0x10
    L0019: mov rbp, [rcx]
    L001c: mov [rsp], rbp
    L0020: lea rbp, [rbp+0x10]
    L0024: lea rax, [L000e]
    L002b: add rsp, 0x10
    L002f: pop rbp
    L0030: ret

Configuration

8.0.724.31311 on x64

Analysis

The assembly appears to set up a stack frame, immediately tear it down, then return. Curiously, there are unreachable instructions after the ret on L0013.

Alternatively, the JIT will elide an empty try/finally block, reducing Class.Method to a simple ret.

https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABABgAJiBGAOgBEBLbAcwDsJcMGxcBuAWABQZSrQAqMBBgGChxAMyUATOQDCAGzy4hAbyHkDlRcRTkAsjAwALCABMAFMACeGGAG0AuuVvYM2AJT6hnqChmHkGFBOQeHkIbGxdDDAAK7MNADqUAyuADIMrDD2AKKskLYF6QCqYgBiABw0AOKWAMqRlfY+fv7+MgnkAL4x4QBmBdjq6tGhCfEDhkmp6Vk5MPmF9gBEW30jYcOzBoeDQA

C# / IL / JIT ASM
using System.Diagnostics;
using System.Text;

public class Class
{
    public void Method(byte[] data)
    {
        try
        {
            Debug.WriteLine(Encoding.UTF8.GetString(data));
        }
        finally
        {
            Debug.WriteLine("");
        }
    }
}
.method public hidebysig 
    instance void Method (
        uint8[] data
    ) cil managed 
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
        01 00 01 00 00
    )
    // Method begins at RVA 0x2050
    // Code size 4 (0x4)
    .maxstack 0

    .try
    {
        IL_0000: leave.s IL_0003
    } // end .try
    finally
    {
        IL_0002: endfinally
    } // end handler

    IL_0003: ret
} // end of method Class::Method
; Core CLR 8.0.724.31311 on x64

Class..ctor()
    L0000: ret

Class.Method(Byte[])
    L0000: ret
@colejohnson66 colejohnson66 added the tenet-performance Performance related issue label Aug 30, 2024
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 30, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Aug 30, 2024
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@timcassell

This comment was marked as outdated.

@colejohnson66 colejohnson66 changed the title JIT does not elide empty try/catch blocks JIT does not elide conditionally empty try/catch blocks Aug 30, 2024
@AndyAyersMS
Copy link
Member

We added try-finally opts because there were quite a few cases where we'd see either empty trys or empty finallys.

I haven't seen the same for (empty)try-catch but perhaps it's worth a look, it would be relatively "easy" to do.

@AndyAyersMS AndyAyersMS removed the untriaged New issue has not been triaged by the area owner label Aug 30, 2024
@AndyAyersMS AndyAyersMS added this to the Future milestone Aug 30, 2024
@colejohnson66 colejohnson66 changed the title JIT does not elide conditionally empty try/catch blocks JIT does not elide empty try/catch blocks Aug 30, 2024
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this issue Nov 29, 2024
If no tree in the try region of a try/catch can throw, then
we can remove the try and delete the catch.

If no tree in the try region of a try/finally can throw, we can
remove the try and inline the finally. This slightly generalizes
the empty-try/finally opt we have been doing for a long time.
(We should do something similar for try/fault, but don't, yet).

Since these optimization passes are cheap, and opportunities
for them arise after other optimizations and unblock subsequent
optimizations, run them early, middle, and late.

Resolves dotnet#107191.

I expect we'll see more of these cases in the future, say if
we unblock cloning of loops with EH.
@dotnet-policy-service dotnet-policy-service bot added the in-pr There is an active PR which will close this issue when it is merged label Nov 29, 2024
eduardo-vp pushed a commit to eduardo-vp/runtime that referenced this issue Dec 5, 2024
If no tree in the try region of a try/catch can throw, then
we can remove the try and delete the catch.

If no tree in the try region of a try/finally can throw, we can
remove the try and inline the finally. This slightly generalizes
the empty-try/finally opt we have been doing for a long time.
(We should do something similar for try/fault, but don't, yet).

Since these optimization passes are cheap, and opportunities
for them arise after other optimizations and unblock subsequent
optimizations, run them early, middle, and late.

Resolves dotnet#107191.

I expect we'll see more of these cases in the future, say if
we unblock cloning of loops with EH.
mikelle-rogers pushed a commit to mikelle-rogers/runtime that referenced this issue Dec 10, 2024
If no tree in the try region of a try/catch can throw, then
we can remove the try and delete the catch.

If no tree in the try region of a try/finally can throw, we can
remove the try and inline the finally. This slightly generalizes
the empty-try/finally opt we have been doing for a long time.
(We should do something similar for try/fault, but don't, yet).

Since these optimization passes are cheap, and opportunities
for them arise after other optimizations and unblock subsequent
optimizations, run them early, middle, and late.

Resolves dotnet#107191.

I expect we'll see more of these cases in the future, say if
we unblock cloning of loops with EH.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI in-pr There is an active PR which will close this issue when it is merged tenet-performance Performance related issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants