13

While playing with C# I found that following snippet:

public int F() 
{
    try 
    {
        return 0;
    } 
    catch (Exception)
    {
        return -1;
    }
}

This generates the following asm:

Program.F()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: push esi
    L0004: sub esp, 0x14
    L0007: xor eax, eax
    L0009: mov [ebp-0x18], eax
    L000c: mov [ebp-0x14], eax
    L000f: mov [ebp-0x10], eax
    L0012: mov [ebp-0xc], eax
    L0015: xor esi, esi
    L0017: jmp short L0023
    L0019: mov esi, 0xffffffff
    L001e: call 0x6fb2d4d3
    L0023: mov eax, esi
    L0025: lea esp, [ebp-4]
    L0028: pop esi
    L0029: pop ebp
    L002a: ret

And when I remove the try and catch block:

public int F() 
{
    return 0;
}

then the generated output is:

Program.F()
    L0000: xor eax, eax
    L0002: ret

Question

As you can see JIT (Release) knows that it'll not return -1 (you can not find any branches that'll jump to return -1 case), but it does generate the try block for return 0 case. The question is can the return statement throw an exception or why does JIT generate asm for it?

Note

In contrast: this is what outputs g++(O2) for C++.

int 
f(void *this_) {
    try {
        return 0;
    }
    catch(...) {
        return -1;
    }
}

asm

f(void*):
        xor     eax, eax
        ret
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • The return itself won't throw an exception. Of course, you can get an exception while evaluating the expression to be returned but that's nothing to do with the actual return since it would do the same thing if assigning that expression to a variable rather than returning it. – paxdiablo Apr 03 '21 at 07:23
  • 1
    @paxdiablo right, but I'm returning 0 there. Can I possibly get an exception here? Maybe I choose wrong set of words. –  Apr 03 '21 at 07:25
  • 1
    Does it make a difference if you remove the `(Exception)` specifier from `catch`? – GSerg Apr 03 '21 at 07:39
  • 1
    I don't think there is any particular reason why the jitter compiled the try...catch this way. Even if it complied to just the xor + ret instructions, the code's behaviour would still be spec-compliant. I would say this is just an artefact of one of the implementation details of the jitter. – Sweeper Apr 03 '21 at 07:39
  • @GSerg no, sadly. https://sharplab.io/#v2:C4LghgzgtgPgAgJgIwFgBQ64GYAEi9IDsO6A3ujpXrgJYB2wOAYgBQCUJaVO5X3VwAE4BPTvyq9x4uMQAMAbgpSAvmKkBjMMHUALJeMlTuMnAFokivuOX6cNtMqA –  Apr 03 '21 at 07:40
  • It [would appear](https://stackoverflow.com/q/1308432/11683) that code inside and outside of `try` blocks is [compiled differently](https://blogs.msmvps.com/peterritchie/2007/06/22/performance-implications-of-try-catch-finally/), so removing a `try` block may result in differences in observable behaviour. – GSerg Apr 03 '21 at 07:49
  • @GSerg that's a different story. Thank you. 1st link might be the answer actually. –  Apr 03 '21 at 07:52
  • 3
    Maybe the developers of the JIT compiler didn't want to waste any time with such an optimization, since a try-catch block around such a trivial return doesn't make sense in the first place. For C++, this can be a different story, since a return statement using a preprocessor macro might look pretty complex to the developer, which would suggest that a try-catch os worth it, but could evaluate to a simple "return 0" after the preprocessing stage without the developer even knowing. – Heinz Kessler Apr 03 '21 at 09:31
  • This happens even with `try { return; } catch { return; }` – tymtam Apr 03 '21 at 10:09

1 Answers1

6

A return statement, in general, can cause an exception due to the general purpose nature of the expression allowed in the statement — in general, an expression can cause an exception.

Should the JIT optimize exception handling knowing that the expression 0 cannot throw?  Perhaps, in a perfect world.  Is anyone complaining about this?

Erik Eidt
  • 23,049
  • 2
  • 29
  • 53