Why is code behavior different in release and debug mode?

Consider the following code:

private static void Main(string[] args) { var ar = new double[] { 100 }; FillTo(ref ar, 5); Console.WriteLine(string.Join(",", ar.Select(a => a.ToString()).ToArray())); } public static void FillTo(ref double[] dd, int N) { if (dd.Length >= N) return; double[] Old = dd; double d = double.NaN; if (Old.Length > 0) d = Old[0]; dd = new double[N]; for (int i = 0; i < Old.Length; i++) { dd[N - Old.Length + i] = Old[i]; } for (int i = 0; i < N - Old.Length; i++) dd[i] = d; } 

The result in debug mode is 100 100 100 100 100. But in Release mode it is: 100 100 100 100.0.

What's happening?

It has been tested using the .NET framework 4.7.1 and .NET Core 2.0.0.

+76
c # .net-core coreclr
Dec 01 '17 at 10:57
source share
2 answers

This seems like a JIT error; I tested with:

 // ... existing code unchanged for (int i = 0; i < N - Old.Length; i++) { // Console.WriteLine(i); // <== comment/uncomment this line dd[i] = d; } 

and adding Console.WriteLine(i) fixes it. The only change in IL:

 // ... L_0040: ldc.i4.0 L_0041: stloc.3 L_0042: br.s L_004d L_0044: ldarg.0 L_0045: ldind.ref L_0046: ldloc.3 L_0047: ldloc.1 L_0048: stelem.r8 L_0049: ldloc.3 L_004a: ldc.i4.1 L_004b: add L_004c: stloc.3 L_004d: ldloc.3 L_004e: ldarg.1 L_004f: ldloc.0 L_0050: ldlen L_0051: conv.i4 L_0052: sub L_0053: blt.s L_0044 L_0055: ret 

against

 // ... L_0040: ldc.i4.0 L_0041: stloc.3 L_0042: br.s L_0053 L_0044: ldloc.3 L_0045: call void [System.Console]System.Console::WriteLine(int32) L_004a: ldarg.0 L_004b: ldind.ref L_004c: ldloc.3 L_004d: ldloc.1 L_004e: stelem.r8 L_004f: ldloc.3 L_0050: ldc.i4.1 L_0051: add L_0052: stloc.3 L_0053: ldloc.3 L_0054: ldarg.1 L_0055: ldloc.0 L_0056: ldlen L_0057: conv.i4 L_0058: sub L_0059: blt.s L_0044 L_005b: ret 

which looks exactly (the only difference is the additional ldloc.3 and call void [System.Console]System.Console::WriteLine(int32) , and another, but equivalent target for br.s ).

I need a JIT fix, I suspect.

Environment:

  • Environment.Version : 4.0.30319.42000
  • <TargetFramework>netcoreapp2.0</TargetFramework>
  • VS: 15.5.0 Preview 5.0
  • dotnet --version : 2.1.1
+67
Dec 01 '17 at 11:15
source share

This is really a build error. x64, .net 4.7.1, build release.

disassembly:

  for(int i = 0; i < N - Old.Length; i++) 00007FF942690ADD xor eax,eax for(int i = 0; i < N - Old.Length; i++) 00007FF942690ADF mov ebx,esi 00007FF942690AE1 sub ebx,ebp 00007FF942690AE3 test ebx,ebx 00007FF942690AE5 jle 00007FF942690AFF dd[i] = d; 00007FF942690AE7 mov rdx,qword ptr [rdi] 00007FF942690AEA cmp eax,dword ptr [rdx+8] 00007FF942690AED jae 00007FF942690B11 00007FF942690AEF movsxd rcx,eax 00007FF942690AF2 vmovsd qword ptr [rdx+rcx*8+10h],xmm6 for(int i = 0; i < N - Old.Length; i++) 00007FF942690AF9 inc eax 00007FF942690AFB cmp ebx,eax 00007FF942690AFD jg 00007FF942690AE7 00007FF942690AFF vmovaps xmm6,xmmword ptr [rsp+20h] 00007FF942690B06 add rsp,30h 00007FF942690B0A pop rbx 00007FF942690B0B pop rbp 00007FF942690B0C pop rsi 00007FF942690B0D pop rdi 00007FF942690B0E pop r14 00007FF942690B10 ret 

The question is located at 00007FF942690AFD, jg 00007FF942690AE7. It bounces back if ebx (which contains 4, the final value of the loop) is larger (jg) than eax, the value of i. This fails when 4 is of course, so it does not write the last element in the array.

It does not work because it registers the value (eax, 0x00007FF942690AF9) and then checks it for 4, but it should still write that value down. It's a little tricky to determine exactly where the problem is, since it looks like it might be the result of an optimization (N-Old.Length), since the debug build contains this code, but the release build predicts this. So for jit people fix;)

+6
Dec 06 '17 at 15:32
source share



All Articles