-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Description
I'm not entirely sure what exactly is going on here, but as far as i understand .NET JIT compilation, Tiered Compilation optimization sometimes produces incorrect code.
Reproduction Steps
internal static class Program
{
public static void Main()
{
const short expectedValue = 6; // could be any number
var i = Swap(expectedValue);
var iteration = 0;
while (true)
{
iteration++;
var value = Swap(i);
if (value != expectedValue)
{
System.Console.WriteLine(iteration);
return;
}
}
}
/// <summary> Perform Endianness swap </summary>
private static unsafe short Swap(short value)
{
var returnValue = short.MinValue;
Swap2((byte*)&value, (byte*)&returnValue);
return returnValue;
}
private static unsafe void Swap2(byte* originalBytes, byte* returnBytes)
{
returnBytes[0] = originalBytes[1];
returnBytes[1] = originalBytes[0];
}
}This snippet is minimal repro of a real scenario. Swap method does endianness reverse, just like System.Buffers.Binary.BinaryPrimitives.ReverseEndianness do. And to be clear - Swap(Swap(x)) should always return x.
Expected behavior
Swap(i) should always return 6, and snippet above should be and infinite loop and never exit.
Actual behavior
After 10k iterations program exits because Swap method starts returning short.MinValue.
Regression?
I've tested the same code on .NET versions from 8 to 10, and it seems to be a problem only on .NET 10. Also, the issue doesn't reproduce on .NET 10 with Debug configuration.
Known Workarounds
We've fixed it for us with following workaround:
public static unsafe short Swap(short value)
{
#if NET10_0_OR_GREATER
return System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
#else
var returnValue = short.MinValue;
Swap2((byte*)&value, (byte*)&returnValue);
return returnValue;
#endif
}But this fixes only the Swap(short) method, but not it overloads with other signatures, such as Swap(Guid) or Swap(double).
Configuration
I've tested it with 2 different environments:
- Ubuntu, .NET 10.0.3, x64 Intel 12gen
- Windows, .NET 10.0.100, x64 Intel 13gen
The behavior is the same on both.
Other information
While investigating what's going on, I found that there is a flag DOTNET_TieredCompilation=0 to disable Tiered compilation. When set, the code above exits immediately after first iteration, not after 10k. That's why i believ the issue is related to tiered compilation, but i'm still not sure about it, correct me if i'm wrong.