I’m writing .NET 8 code to generate bit masks of various sizes. These masks are generated on the “hot path” in my code, so performance is important. I have the following method:
public static ulong Mask(int bitsize)
{
return (1ul << bitsize) - 1ul;
}
used in the following way:
using System;
var r = new ulong[3];
test(r);
foreach (var u in r)
{
Console.WriteLine(u);
}
void test(ulong [] result) {
result[0] = mask(32);
result[1] = mask(63);
result[2] = mask(64);
}
static ulong mask(int bitsize)
{
return (1ul << bitsize) - 1u;
}
My expectation was that I should be able to create a mask of “all ones” by calling mask(64)
. This would shift in 64 zero bits, from which 1 is subtracted. Instead, I’m seeing the JITter generate “all zeros”. Wrapping the returned value in unchecked
makes no difference.
This reminds me of undefined behavior in C++: if you do <forbidden-thing>
, anything can happen in the program. The subtraction after the shift seems to have been completely lost.
Naturally, I can pre-calculate the mask values for bitsize
ranging from 0 to 64, and do a table lookup, but a table lookup is going to be bigger/slower than simply:
mov eax, 1
shlx rax, rax, rcx
dec rax
ret
which the JITter can aggressively inline for great success.
So, is 1ul << 64
undefined behavior in C#? Is there another way to generate bitmasks dynamically without introducing branches in the code?