Rozważ następujący kod:
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;
}
Wynik w trybie debugowania to: 100,100,100,100,100. Ale w trybie wydania jest to: 100,100,100,100,0.
Co się dzieje?
Został przetestowany przy użyciu .NET Framework 4.7.1 i .NET Core 2.0.0.
Console.WriteLine(i);
do końcowej pętli (dd[i] = d;
) "naprawia" to, co sugeruje błąd kompilatora lub błąd JIT; patrząc na IL ...if (dd.Length >= N) return;
, co może być prostszą repliką.Odpowiedzi:
Wygląda na to, że jest to błąd JIT; Testowałem z:
// ... existing code unchanged for (int i = 0; i < N - Old.Length; i++) { // Console.WriteLine(i); // <== comment/uncomment this line dd[i] = d; }
i dodanie
Console.WriteLine(i)
poprawek. Jedyna zmiana IL to:// ... 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
vs
// ... 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
co wygląda dokładnie dobrze (jedyną różnicą jest dodatkowy
ldloc.3
icall void [System.Console]System.Console::WriteLine(int32)
oraz inny, ale równoważny cel dlabr.s
).Podejrzewam, że będzie to wymagało poprawki JIT.
Środowisko:
Environment.Version
: 4.0.30319.42000<TargetFramework>netcoreapp2.0</TargetFramework>
dotnet --version
: 2.1.1źródło
To rzeczywiście błąd zespołu. x64, .net 4.7.1, kompilacja wydania.
demontaż:
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
Problem jest pod adresem 00007FF942690AFD, jg 00007FF942690AE7. Skacze z powrotem, jeśli ebx (który zawiera 4, wartość końcową pętli) jest większy (jg) niż eax, wartość i. To kończy się niepowodzeniem, gdy jest to oczywiście 4, więc nie zapisuje ostatniego elementu w tablicy.
Nie udaje się, ponieważ wartość rejestru inc jest i (eax, na 0x00007FF942690AF9), a następnie sprawdza ją z 4, ale nadal musi zapisać tę wartość. Trochę trudno jest dokładnie określić, gdzie dokładnie znajduje się problem, ponieważ wygląda na to, że może być wynikiem optymalizacji (N-Old.Length), ponieważ kompilacja debugowania zawiera ten kod, ale kompilacja wydania oblicza to wstępnie. Więc to dla jitów do naprawienia;)
źródło