Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

It appears some parts of an expression may be evaluated at compile-time, while other parts at run-time

Tags:

c#

Probably a silly question, since I may have already answered my question, but I just want to be sure that I'm not missing something

Constant expressions are evaluated at compile time within checked context. I thought the following expression shouldn't be evaluated at compile time, since I assumed C# considers a particular expression as a constant expression only if all operands on the left-hand side are constants:

int i= 100;
long u = (int.MaxValue  + 100 + i); //error

Instead it appears compiler considers any subexpression where both operands are constants as a constant expression, even if other operands in an expression are non-constants? Thus compiler may only evaluate a part of an expression at compile time, while the remained of the expression ( which contains non-constant values ) will get evaluated at run-time --> I assume in the following example only (200 +100) gets evaluated at compile time

int i=100;
long l = int.MaxValue  + i + ( 200 + 100 ); // works

Are my assumptions correct?

thanx

like image 569
user702769 Avatar asked Apr 14 '11 19:04

user702769


People also ask

Is constant expressions are evaluated at compile time?

A constant expression gets evaluated at compile time, not run time, and can be used in any place that a constant can be used. The constant expression must evaluate to a constant that is in the range of representable values for that type.

What is checked during compile time?

During compile time the compiler check for the syntax, semantic, and type of the code.

Is Constexpr always evaluated at compile time?

A constexpr function that is eligible to be evaluated at compile-time will only be evaluated at compile-time if the return value is used where a constant expression is required. Otherwise, compile-time evaluation is not guaranteed.

What is used for a function to execute at compile time?

In computing, compile-time function execution (or compile time function evaluation, or general constant expressions) is the ability of a compiler, that would normally compile a function to machine code and execute it at run time, to execute the function at compile time.


1 Answers

doec C# classify any subexpression where both operands are constants as a constant expression, even if other operands in an expression are non-constants?

The answer to your question is "yes," but it is important to clearly understand what constitutes a "subexpression".

I assume in "int.MaxValue + i + ( 200 + 100 )" only (200 + 100) gets evaluated at compile time

Correct. Now, if you had said instead "int.MaxValue + i + 200 + 100" then "200 + 100" would never be evaluated at all because that's not a subexpression due to associativity.

This is a bit subtle. Let me explain in detail.

First off, let's distinguish between de jure compile time constants and de facto compile time constants. Let me give you an example. Consider this method body:

const int x = 123;
int y = x + x;
if (y * 0 != 0) Console.WriteLine("WOO HOO");
M(ref y);

In C# 1 and 2 if you compiled that with optimizations on, that would be compiled as if you had written:

int y = 246;
M(ref y);

The constant x vanishes, y is initialized to the constant-folded expression, and the arithmetic optimizer realizes that any local integer times zero is never not equal to zero, so it optimizes that away as well.

In C# 3 I accidentally introduced a bug in the optimizer which was not fixed in C# 4 either. In C# 3/4 we'd generate that as

int y = 246;
bool b = false;
if (b) Console.WriteLine("WOO HOO");
M(ref y);

That is, the arithmetic is optimized away, but we don't go the step further and optimize away the "if(false)".

The difference is that the constant folding behaviour on x + x is guaranteed to happen at compile time, but the constant folding behaviour on the partially-variable expression y*0 is not.

I regret the error and apologize. However, the reason I changed this in C# 3 and accidentally introduced a bug in the code generator was to fix a bug in the semantic analyzer. In C# 2.0 this was legal:

int z;
int y = 246;
if (y * 0 == 0) z = 123;
Console.WriteLine(z);

That should not be legal. You know and I know that y * 0 == 0 will always be true, and therefore z is assigned before it is read, but the spec says that this analysis must only be performed if the expression in the "if" is a compile-time constant, and that's not a compile-time constant because it contains a variable. We took the breaking change for C# 3.0.

So, OK, let's assume that you understand the difference between a de jure constant that must be evaluated because the spec says so, and a de facto constant that is evaluated because the optimizer is smart. Your question is under what circumstances can an expression be de jure and de facto partially "folded" at compile time? (By "folded" I mean resolving an expression containing constants into a simpler expression.)

The first thing we have to consider is the associativity of the operators. Only once we have done the associativity and precedence analysis do we know what is and is not a subexpression. Consider

int y = 3;
int x = 2 - 1 + y;

Addition and subtraction are left-associative, as are most operators in C#, so that is the same as

int x = (2 - 1) + y;

Now clearly (2 - 1) is a de jure constant expression, so that is folded and becomes 1.

If on the other hand you said

int x = y + 2 - 1;

That is

int x = (y + 2) - 1;

and it is not folded because those are two non-constant expressions.

This could have a real effect in a checked context. If y is int.MaxValue - 1 then the first version will not overflow but the second version will!

Now, the compiler and optimizer are permitted to say "well, I happen to know that this is an unchecked context, and I happen to know that I can turn that into "y + 1" safely, so I will." The C# compiler does not do this but the jitter might. In your particular example,

long l = int.MaxValue + i + 200 + 100 ; 

is actually code-gen'd by the C# compiler as

long l = ((int.MaxValue + i) + 200) + 100 ; 

And not

long l = (int.MaxValue + i) + 300; 

The jitter may choose to make that optimization if it so wishes and can prove that doing so is safe.

But

long l = int.MaxValue + i + (200 + 100); 

would of course be generated as

long l = (int.MaxValue + i) + 300; 

However, we do perform the optimization you want on strings in the C# compiler! If you say

string y = whatever;
string x = y + "A" + "B" + "C";

then you might think, well, that's a left-associative expression so that's:

string x = ((y + "A") + "B") + "C";

And therefore there will be no constant folding. However, we actually detect this situation and at compile time do the folding to the right, so we generate this as if you'd written

string x = y + "ABC";

Thereby saving the cost of the concatenations at runtime. The string concat optimizer is actually reasonably sophisticated about recognizing various patterns for gluing strings together at compile time.

We could do the same for unchecked arithmetic. We just haven't gotten around to it.

like image 60
Eric Lippert Avatar answered Sep 20 '22 05:09

Eric Lippert