Note: I noticed some errors in my posted example - editing to fix it
The official C# compiler does some interesting things if you don't enable optimization.
For example, a simple if statement:
int x;
// ... //
if (x == 10)
// do something
becomes something like the following if optimized:
ldloc.0
ldc.i4.s 10
ceq
bne.un.s do_not_do_something
// do something
do_not_do_something:
but if we disable optimization, it becomes something like this:
ldloc.0
ldc.i4.s 10
ceq
ldc.i4.0
ceq
stloc.1
ldloc.1
brtrue.s do_not_do_something
// do something
do_not_do_something:
I can't quite get my head around this. Why all that extra code, which is seemingly not present in the source? In C#, this would be the equivalent of:
int x, y;
// ... //
y = x == 10;
if (y != 0)
// do something
Does anyone know why it does this?
I don't fully understand the point of the question. It sounds like you're asking "why does the compiler produce unoptimized code when the optimization switch is off?" which kinda answers itself.
However, I'll take a stab at it. I think the question is actually something like "what design decision causes the compiler to emit a declaration, store and load of local #1, which can be optimized away?"
The answer is because the unoptimized codegen is designed to be clear, unambiguous, easy to debug, and to encourage the jitter to generate code that does not aggressively collect garbage. One of the ways we achieve all those goals is to generate locals for most values that go on the stack, even temporary values. Let's take a look at a more complicated example. Suppose you have:
Foo(Bar(123), 456)
We could generate this as:
push 123
call Bar - this pops the 123 and pushes the result of Bar
push 456
call Foo
That is nice and efficient and small, but it does not meet our goals. It is clear and unambiguous, but it is not easy to debug because the garbage collector could get aggressive. If Foo for some reason does not actually do anything with its first argument then the GC is allowed to reclaim the return value of Bar before Foo runs.
In the unoptimized build we would generate something more like
push 123
call Bar - this pops the 123 and pushes the result of Bar
store the top of the stack in a temporary location - this pops the stack, and we need it back, so
push the value in the temporary location back onto the stack
push 456
call Foo
Now the jitter has a big hint that says "hey jitter, keep that alive in the local for a while even if Foo doesn't use it"
The general rule here is "make local variables out of all temporary values in the unoptimized build". And so there you go; in order to evaluate the "if" statement we need to evaluate a condition and convert it to bool. (Of course the condition need not be of type bool; it could be of a type implicitly convertible to bool, or a type that implements an operator true/operator false pair.) The unoptimized code generator has been told "aggressively turn all temporary values into locals", and so that's what you get.
I suppose in this case we could suppress that on temporaries that are conditions in "if" statements, but that sounds like making work for me that has no customer benefit. Since I have a stack of work as long as your arm that does have tangible customer benefit, I'm not going to change the unoptimized code generator, which generates unoptimized code, exactly as it is supposed to.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With