Skip to content

Bug with Tiered Compilation optimizations on .NET 10 #125301

@lightmg

Description

@lightmg

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIbug

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions