"Unreachable code detected" in MSIL or Native code
Asked Answered
D

3

5

Does compiler compile "Unreachable Codes" in MSIL or Native code in run time?

Dame answered 27/12, 2011 at 11:10 Comment(4)
Your question needs more context, because not all MSIL is generated by a compiler. When do you get the error? What is the assembly that you are loading expected to do? Is it yours, or third party?Chaplin
It just comes from my curiosity. A sample code is if(1==1){//Reachable}else{//Unreachable code detected}. Does the unreachable part compile? Does it allocate memory?Dame
Are you specifically asking about unreachable code that the compiler knows and warns is unreachable, or are you also asking about more complicated cases such as if (i == 1) where you, the programmer, know that i will always be an even number?Matias
possible duplicate of Performance differences between debug and release buildsJute
H
13

The question is a bit unclear but I'll take a shot at it.

First off, Adam's answer is correct insofar as there is a difference in the IL that the compiler emits based on whether the "optimize" switch is on or off. The compiler is much more aggressive about removing unreachable code with the optimize switch on.

There are two kinds of unreachable code that are relevant. First there is de jure unreachable code; that is, code that the C# language specification calls out as unreachable. Second, there is de facto unreachable code; that is code that the C# specification does not call out as unreachable, but nevertheless, cannot be reached. Of the latter kind of unreachable code, there is code that is known to the optimizer to be unreachable, and there is code not known to the optimizer to be unreachable.

The compiler typically always removes de jure unreachable code, but only removes de facto unreachable code if the optimizer is turned on.

Here's an example of each:

int x = 123;
int y = 0;
if (false) Console.WriteLine(1);
if (x * 0 != 0) Console.WriteLine(2);
if (x * y != 0) Console.WriteLine(3);

All three Console.WriteLines are unreachable. The first is de jure unreachable; the C# compiler states that this code must be treated as unreachable for the purposes of definite assignment checking.

The second two are de jure reachable but de facto unreachable. They must be checked for definite assignment errors, but the optimizer is permitted to remove them.

Of the two, the optimizer detects the (2) case but not the (3) case. The optimizer knows that an integer multiplied by zero is always zero, and that therefore the condition is always false, so it removes the entire statement.

In the (3) case the optimizer does not track the possible values assigned to y and determine that y is always zero at the point of the multiplication. Even though you and I know that the consequence is unreachable, the optimizer does not know that.

The bit about definite assignment checking goes like this: if you have an unreachable statement then all local variables are considered to be assigned in that statement, and all assignments are considered to not happen:

int z;
if (false) z = 123;
Console.WriteLine(z); // Error
if (false) Console.WriteLine(z); // Legal

The first usage is illegal because z has not been definitely assigned when it is used. The second usage is not illegal because the code isn't even reachable; z can't be used before it is assigned because control never gets there!

C# 2 had some bugs where it confused the two kinds of reachability. In C# 2 you could do this:

int x = 123;
int z;
if (x * 0 != 0) Console.WriteLine(z);

And the compiler would not complain, even though de jure the call to Console.WriteLine is reachable. I fixed that in C# 3.0 and we took the breaking change.

Note that we reserve the right to change up how the unreachable code detector and code generator work at any time; we might decide to always emit the unreachable code or never emit it or whatever.

Harvison answered 27/12, 2011 at 16:25 Comment(0)
P
3

The C# compiler can compile your application in either a debug configuration or a release configuration. This changes the behavior of whether or not unreachable code is compiled down to CIL and emitted into the output executable. Let's take a simple function for example:

public static int GetAnswer()
{
    return 42;
    Console.WriteLine("Never getting here!");
}

When you compile this in a debug configuration, the entire method (including the unreachable code) is emitted as CIL. It would look something like this (perhaps with some added nop instructions to assist debugging):

.method public static int32 GetAnswer() cil managed
{
    .maxstack 1
    .locals init (int32)

            ldc.i4.s 42 // load the constant 42 onto the stack
            stloc.0 // pop that 42 from the stack and store it in a local
            br.s L_000C // jump to the code at L_000C

            ldstr "Never getting here!" // load the string on the stack
            call void [mscorlib]System.Console::WriteLine(string) // call method

    L_000C: ldloc.0 // push that 42 onto the stack from the local
            ret // return, popping the 42 from the stack
}

The reason all this code is emitted is so the debugger can allow you to manually step into unreachable code, perhaps to force it to run under debugging circumstances.

That being said, when you build your project under a release configuration, the compiler will realize that, because the built assembly won't be stepped through in a debugger, it won't emit any unreachable code. The CIL would look like this:

.method public static int32 GetAnswer() cil managed
{
   .maxstack 1

    ldc.i4.s 42 // load the constant 42 onto the stack
    ret // return, popping the 42 from the stack
}

Simple, clean, and optimized.

Peggi answered 27/12, 2011 at 11:59 Comment(5)
Again (same as with the other answer): did you check? Because the compiler on my system does not include code it knows is unreachable in the generated executable, not even in debug mode.Matias
So, I just tested this out with Visual Studio 2010 SP1 and .NET Framework 4; here's the strange part: in debug mode, the compiler did emit the extra storage of the local (as opposed to just using the stack for the literal 42) and the unconditional branch, but it didn't emit the call to Console::WriteLine(string). I'm baffled... I do know that I've seen this behavior before, perhaps in previous versions of the compiler or the .NET Framework (hence why I could write out that CIL by hand) so it appears something may have changed, or there are forces at play that I'm unaware of.Peggi
Where's Eric Lippert when we need him?Peggi
Good point, it's very well possible this has changed, and since the observable behaviour of both possible generated executables is the same, it could in fact silently change again with newer versions of .NET Framework and/or the compiler.Matias
Indeed. I've actually sent Mr. Lippert an email; hopefully he can take a look at this and shed some light. I'm damned curious now!Peggi
S
0

In release mode certain code blocks may get compiled out, but that's not what that error message means. So, to answer your question, code such as:

if (false)
 // do something

would never make it into bytecode unless you have debugging enabled (that's because if you attach a debugger you could manually step into that if statement, so the code needs to be there).

When you receive the error message that debugging can't continue because of unreachable code, that tends to mean that the process you are debugging does not line up with the source code you are using (different versions, etc...).

Scharf answered 27/12, 2011 at 11:42 Comment(3)
Have you checked? Because I have, and what I see does not match up with what you describe. If I try to force the debugger to step into an unreachable code block, it simply doesn't. It selects the end of the block as the next instruction.Matias
Some code won't be compiled regardless. Make sure your unreachable code does something other than allocate memory. The debugger always steps over variable declaration. So in the if statement, declare a variable and then use it. But to answer your question, I have not specifically checked this example.Scharf
My example consists of static void Main() { if (1 == 0) { Console.WriteLine("Universe has exploded"); } }, and the generated IL contains no call to Console.WriteLine.Matias

© 2022 - 2024 — McMap. All rights reserved.