可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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
{
[Test]
public void TestChecker()
{
var checker = new Checker();
Assert.That(checker.IsDateTime(DateTime.Now), Is.True);
}
}
public class Checker
{
public bool IsDateTime(object o)
{
return o is DateTime;
}
}
It seems code optimization wreaks some havoc; if I disable it on the Release build, it works as well. That was rather puzzling to me. Below, I've used ILDASM to disassemble the 2 versions of the build:
Debug IL:
.method public hidebysig instance bool IsDateTime(object o) cil managed
{
// Code size 15 (0xf)
.maxstack 2
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: isinst [mscorlib]System.DateTime
IL_0007: ldnull
IL_0008: cgt.un
IL_000a: stloc.0
IL_000b: br.s IL_000d
IL_000d: ldloc.0
IL_000e: ret
} // end of method Validator::IsValid
Release IL:
.method public hidebysig instance bool IsDateTime(object o) cil managed
{
// 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 Validator::IsValid
It seems a store and load is optimized away. Targeting earlier versions of the .NET framework made the problem go away, but that may just be a fluke. I found this behaviour somewhat unnerving, can anybody explain why the compiler would think it safe to do an optimization that produces different observable behaviour?
Thanks in advance.
回答1:
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)]
回答2:
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.
回答3:
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.
回答4:
For reference I checked with mono
- Mono JIT compiler version 2.6.7 (Debian 2.6.7-3ubuntu1)
- Mono JIT compiler version 2.8.2 (sehe/d1c74ad Fri Feb 18 21:46:52 CET 2011)
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