Below is a simple test fixture. It succeeds in Debug builds and fails in Release builds (VS2010, .NET4 solution, x64):
[TestFixture]
public sealed class Test
{
The store and load is essentially a nop as far as flow-control goes, but probably massage some CPU caches in some way. The actual flow just loads the argument on the stack, checks if it's an instance (which return null or the instance), pushes null onto the stack, and compares (greater-than) which results in a boolean being left on the stack.
Now what the JITter does with it is another story altogether (and would depend on what platform you are using. The JITter will do all sorts of crazy things in the name of performance (our team recently got hit because tail-call optimization changed to optimize across domain boundaries which broke GetCallingAssembly()). It's possible the JITter is inlining IsDateTime, noticing that there's not way it can't not be a DateTime and just pushing true onto the stack.
It's also possible your release version is targetting a slightly different Framework so DateTime in the test assembly is not DateTime in the tested assembly.
I realize that doesn't answer why your code is breaking.
This bug already came up in this SO question by Jacob Stanley. Jacob has already reported the bug, and Microsoft has confirmed that it is indeed a bug in the CLR JIT. Microsoft had this to say:
This bug will be fixed in a future version of the runtime. I'm afraid it's too early to tell if that will be in a service pack or the next major release.
Thank you again for reporting the issue.
You should be able to work around the bug by adding the following attribute to TestChecker()
:
[MethodImpl(MethodImplOptions.NoInlining)]
It isn't related to the C# compiler, the IL is identical. You found a bug in the .NET 4.0 jitter optimizer. You can repro it in Visual Studio. Tools + Options, Debugging, General, untick the "Suppress JIT optimization on module load" option and run the Release build to repro the failure.
I haven't looked at it closely enough yet to identify the bug. It looks very strange, it inlines the method and completely omits the code for the boxing conversion. The machine code is substantially different from the code generated by the version 2 jitter.
A clean workaround isn't that easy, you can do it by suppressing inlining. Like this:
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public bool IsDateTime(object o) {
return o is DateTime;
}
You can report the bug at connect.microsoft.com. Let me know if you don't want to and I'll take care of it.
Never mind, that was already done. It wasn't fixed in the maintenance release that was included with VS2010 SP1.
This bug has been fixed, I can no longer repro it. My current version of clrjit.dll is 4.0.30319.237 dated May 17th 2011. I can't tell exactly what update repaired it. I got a security update on Aug 5th 2011 that updated clrjit.dll to revision 235 with a date of Apr 12, that would be the earliest.
For reference I checked with mono
Both presented no problems whatsoever. Here is the IL with optimization in 2.8.2
.method public hidebysig
instance default bool IsDateTime (object o) cil managed
{
// Method begins at RVA 0x2130
// Code size 10 (0xa)
.maxstack 8
IL_0000: ldarg.1
IL_0001: isinst [mscorlib]System.DateTime
IL_0006: ldnull
IL_0007: cgt.un
IL_0009: ret
} // end of method Checker::IsDateTime
Without optimizations is exactly the same
Here is the result of mono's jitted code for this IL:
00000130 <TestData_Checker_IsDateTime_object>:
130: 55 push %ebp
131: 8b ec mov %esp,%ebp
133: 53 push %ebx
134: 56 push %esi
135: 83 ec 10 sub $0x10,%esp
138: e8 00 00 00 00 call 13d <TestData_Checker_IsDateTime_object+0xd>
13d: 5b pop %ebx
13e: 81 c3 03 00 00 00 add $0x3,%ebx
144: 8b 45 0c mov 0xc(%ebp),%eax
147: 89 45 f4 mov %eax,-0xc(%ebp)
14a: 8b 75 0c mov 0xc(%ebp),%esi
14d: 83 7d 0c 00 cmpl $0x0,0xc(%ebp)
151: 74 1a je 16d <TestData_Checker_IsDateTime_object+0x3d>
153: 8b 45 f4 mov -0xc(%ebp),%eax
156: 8b 00 mov (%eax),%eax
158: 8b 00 mov (%eax),%eax
15a: 8b 40 08 mov 0x8(%eax),%eax
15d: 8b 48 08 mov 0x8(%eax),%ecx
160: 8b 93 10 00 00 00 mov 0x10(%ebx),%edx
166: 33 c0 xor %eax,%eax
168: 3b ca cmp %edx,%ecx
16a: 0f 45 f0 cmovne %eax,%esi
16d: 85 f6 test %esi,%esi
16f: 0f 97 c0 seta %al
172: 0f b6 c0 movzbl %al,%eax
175: 8d 65 f8 lea -0x8(%ebp),%esp
178: 5e pop %esi
179: 5b pop %ebx
17a: c9 leave
17b: c3 ret
17c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi