Bit specific code in agnostic assemblies???

In previous blog entries I’ve spent some time talking about how to mark assemblies as bit specific and how the loader deals with those markings.

What however is the preferred mode of an application? I will posit that it is to be compiled agnostic and to run equally well on both 32-bit and 64-bit platforms. It makes a lot of things easier: development, build, testing, deployment, servicing…

Caveat: The following discussion deals only with fully IL assemblies. If you generate managed C++ code you may end up with some native code in your image at which point it has to be tied to one specific platform.

If you have a reason to tie yourself to only one platform (e.g. x86 because you only have an x86 version of some native DLL that you need to P/Invoke to) then your decision is easy and it’s been made for you. Just flip the /platform:x86 switch on your compiler and go. However, if you have some code that works on both 32-bit and 64-bit platforms with just some subtle difference then you have a couple of options to think about for implementing the differences:

1) Use compile time defines (#if/#else in C#) to separate your 64-bit code from 32-bit code. Use the /platform:X switch of your compiler to generate different assemblies for 32-bit and (both) 64-bit platforms.
2) Use runtime if/else blocks to separate 32-bit and 64-bit code.

Both of these end up having their place. In most cases I’ve seen, people have only a small amount of code which needs to be bit specific, and in those cases dealing with the rest of the hassles around building and deploying multiple assemblies aren’t really worth it…

But, what about the runtime cost of the check? What if your bit specific code is on the hot path? Won’t that hurt?

Actually, that’s the cool part, if you do it right it won’t[1]. So, what are your options for determining bitness of a process at runtime?

A) if (Marshal.SizeOf(IntPtr.GetType()) == 8) {/*64*/} else {/*32*/ }
B) if (IntPtr.Size == 8) {/*64*/} else {/*32*/}
C) readonly static bool is64Bit = (IntPtr.Size==8);
if (is64Bit) {/*64*/} else {/*32*/}

Of those options there are 2 right ways and a wrong way. Unfortunately, some of the early information coming out of Microsoft indicated that you should use Marshal.SizeOf, which is definitely the wrong way to do this. That check involves a call to the marshaling code in mscorlib.dll and since the JIT (or ngen) compiler doesn’t know at JIT (or ngen) time what the result will be the unused half of the code can’t be optimized away as dead code.

The easiest way to do this is B, since IntPtr.Size is a constant which is hard-coded into mscorlib.dll when we build the runtime, the JIT (or ngen) can check the loaded mscorlib.dll (which will vary depending on bitness) and optimize away the check and the unused half of the code.

Option C works also, but it has a potentially subtle bug to it. If you don’t mark the static variable definition as readonly then the JIT (and ngen) won’t be able to optimize away the check and unused code. This is because it has to assume that the value can change at runtime. This is very important to remember because without this keyword this solution will become almost as bad as A.

Recommendation: for simple cases, use “if (IntPtr.Size==8)” to determine 64-bitness. For more complex cases consider using a static boolean, but remember to mark it as readonly.

Unfortunately the if/else solution won’t work well for cases where you need different structure definitions on 64-bit and 32-bit platforms for P/Invoke-ing to native routines. If you have a very small number of usages you might consider having two separate structure definitions and P/Invoke declarations, and using if/else to determine which one you use (maybe hiding the bitness stuff behind a wrapper). However, if it is a frequently used structure then it probably makes more sense to just use platform specific assemblies and compile time defines to determine structure layout as then changes only need to be made at the structure definition site.

If you’d like to see some of this stuff in action I’ve posted the source to a test that you can run and then inspect in the debugger to see what the JIT does ( I’m sure it can be done more easily in VS, but I’ve been using WinDbg with SOS’s !name2ee and !u commands to disassemble the resulting code.

[1] Well, there is a small cost involved in the JIT having to parse the extra IL code for both platforms before it can evaluate the const condition and throw away half. However this cost is minimal and for frequently executed code is trivial. For ngen’d code the cost at runtime is non-existant.