For code such as this:
int res = 0; for (int i = 0; i < 32; i++) { res += 1 << i; }
This code is generated (release mode, no debugger attached, 64bit):
xor edx,edx mov r8d,1 _loop: lea ecx,[r8-1] and ecx,1Fh ; why? mov eax,1 shl eax,cl add edx,eax mov ecx,r8d and ecx,1Fh ; why? mov eax,1 shl eax,cl add edx,eax lea ecx,[r8+1] and ecx,1Fh ; why? mov eax,1 shl eax,cl add edx,eax lea ecx,[r8+2] and ecx,1Fh ; why? mov eax,1 shl eax,cl add edx,eax add r8d,4 cmp r8d,21h jl _loop
Now I can see the point of most instructions there, but what's up with the AND instructions? ecx will never be more than 0x1F in this code anyway, but I excuse it for not noticing that (and also for not noticing that the result is a constant), it's not an ahead-of-time compiler that can afford to spend much time on analysis after all. But more importantly, SHL with a 32bit operand masks cl by 0x1F already. So it seems to me that these ANDs are entirely useless. Why are they generated? Do they have some purpose I'm missing?
A computer instruction is an order given to a computer processor by a computer program. At the lowest level, each instruction is a sequence of 0s and 1s that describes a physical operation the computer is to perform.
While it is possible to write programs directly in machine code, managing individual bits and calculating numerical addresses and constants manually is tedious and error-prone.
The and
is already present in the CIL code emitted by the C# compiler:
IL_0009: ldc.i4.s 31 IL_000b: and IL_000c: shl
The spec for the CIL shl
instruction says:
The return value is unspecified if shiftAmount is greater than or equal to the size of value.
The C# spec, however, defines the 32-bit shift to take the shift count mod 32:
When the type of x is
int
oruint,
the shift count is given by the low-order five bits of count. In other words, the shift count is computed fromcount & 0x1F
.
In this situation, the C# compiler can’t really do much better than emit an explicit and
operation. Best you can hope for is that the JITter will notice this and optimize away the redundant and
, but that takes time, and the speed of JIT is pretty important. So consider this the price paid for a JIT-based system.
The real question, I guess, is why the CIL specifies the shl
instruction that way, when C# and x86 both specify the truncating behaviour. That I do not know, but I speculate that it’s important for the CIL spec to avoid specifying a behaviour that may JIT to something expensive on some instruction sets. At the same time, it’s important for C# to have as few undefined behaviours as possible, because people invariably end up using such undefined behaviours until the next version of the compiler/framework/OS/whatever changes them, breaking the code.
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