Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Unreachable code detected" in MSIL or Native code

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

like image 789
Babak Avatar asked Dec 27 '11 11:12

Babak


People also ask

What is unreachable code detected?

Unreachable code detected. The compiler detected code that will never be executed.

What is unreachable code in Turbo C++?

Unreachable statements are statements that will not be executed during the program execution.

What is unreachable code in Visual Studio?

Unreachable code is a program code fragment which is never executed. Don't mix it up with dead code which is, unlike unreachable code, a code fragment that can be executed but whose result is never used in any other computation.


2 Answers

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.

like image 196
Eric Lippert Avatar answered Oct 12 '22 23:10

Eric Lippert


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.

like image 37
Adam Maras Avatar answered Oct 13 '22 00:10

Adam Maras