Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test failure: JIT/IL_Conformance/Convert/TestConvertFromIntegral/TestConvertFromIntegral.dll #112329

Closed
v-wenyuxu opened this issue Feb 10, 2025 · 25 comments
Assignees
Labels
arch-x64 area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI blocking-clean-ci-optional Blocking optional rolling runs os-windows
Milestone

Comments

@v-wenyuxu
Copy link

Failed in: runtime-coreclr r2r-extra 20250208.1

Failed tests:

R2R-CG2 windows x64 Checked jitstressregs8 @ Windows.10.Amd64.Open
    - JIT/IL_Conformance/Convert/TestConvertFromIntegral/TestConvertFromIntegral.dll

Error message:

 Assert.Equal() Failure: Values differ
Expected: 100
Actual:   101

Stack trace:

   at Xunit.Assert.Equal[T](T expected, T actual, IEqualityComparer`1 comparer) in /_/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs:line 174
   at Program.<<Main>$>g__TestExecutor399|0_400(StreamWriter tempLogSw, StreamWriter statsCsvSw, <>c__DisplayClass0_0&)
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Feb 10, 2025
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Feb 10, 2025
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@amanasifkhalid
Copy link
Member

I suspect this is a register allocation issue. Test output:

No exception in DynamicConvertFromSystem.SingleToSystem.SByte1Opconv.ovf.i1
Wrong result in DynamicConvertFromSystem.SingleToSystem.SByte0Opconv.ovf.i1.un
No exception in DynamicConvertFromSystem.SingleToSystem.SByte0Opconv.ovf.i1.un
Wrong result in DynamicConvertFromSystem.SingleToSystem.Byte0Opconv.ovf.u1.un
Wrong result in DynamicConvertFromSystem.SingleToSystem.Int160Opconv.ovf.i2.un
Wrong result in DynamicConvertFromSystem.SingleToSystem.UInt160Opconv.ovf.u2.un
Wrong result in DynamicConvertFromSystem.SingleToSystem.UInt320Opconv.ovf.u4.un
Wrong result in DynamicConvertFromSystem.SingleToSystem.Int640Opconv.ovf.i8.un
Wrong result in DynamicConvertFromSystem.SingleToSystem.UInt640Opconv.ovf.u8.un
No exception in DynamicConvertFromSystem.DoubleToSystem.SByte1Opconv.ovf.i1
Not expected exception in DynamicConvertFromSystem.DoubleToSystem.SByte9.223372036854776E+18Opconv.ovf.i1.un
Not expected exception in DynamicConvertFromSystem.DoubleToSystem.ByteNaNOpconv.ovf.u1.un
Not expected exception in DynamicConvertFromSystem.DoubleToSystem.Int16NaNOpconv.ovf.i2.un
Not expected exception in DynamicConvertFromSystem.DoubleToSystem.UInt16NaNOpconv.ovf.u2.un
Not expected exception in DynamicConvertFromSystem.DoubleToSystem.Int32NaNOpconv.ovf.i4.un
Not expected exception in DynamicConvertFromSystem.DoubleToSystem.UInt32NaNOpconv.ovf.u4.un
Not expected exception in DynamicConvertFromSystem.DoubleToSystem.Int64NaNOpconv.ovf.i8.un
Not expected exception in DynamicConvertFromSystem.DoubleToSystem.UInt64NaNOpconv.ovf.u8.un

Repros with the following:

set DOTNET_TieredCompilation=0
set DOTNET_JitStressRegs=8

cc @dotnet/jit-contrib

@JulieLeeMSFT
Copy link
Member

@kunalspathak, PTAL.

@amanasifkhalid
Copy link
Member

This might be fixed by #112217. I would wait for that to go in before starting to work on a different fix.

@v-wenyuxu
Copy link
Author

Failed in: runtime-coreclr r2r-extra 20250209.1

Failed tests:

R2R-CG2 windows x64 Checked jitstressregs8 @ Windows.10.Amd64.Open
    - JIT/IL_Conformance/Convert/TestConvertFromIntegral/TestConvertFromIntegral.dll

Error message:

 Assert.Equal() Failure: Values differ
Expected: 100
Actual:   101

Stack trace:

   at Xunit.Assert.Equal[T](T expected, T actual, IEqualityComparer`1 comparer) in /_/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs:line 174
   at Program.<<Main>$>g__TestExecutor399|0_400(StreamWriter tempLogSw, StreamWriter statsCsvSw, <>c__DisplayClass0_0&)

@v-wenyuxu
Copy link
Author

Failed in: runtime-coreclr r2r-extra 20250215.1

Failed tests:

R2R-CG2 windows x64 Checked jitstressregs8 @ Windows.10.Amd64.Open
    - JIT/IL_Conformance/Convert/TestConvertFromIntegral/TestConvertFromIntegral.dll

Error message:

 Assert.Equal() Failure: Values differ
Expected: 100
Actual:   101

Stack trace:

   at Xunit.Assert.Equal[T](T expected, T actual, IEqualityComparer`1 comparer) in /_/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs:line 174
   at Program.<<Main>$>g__TestExecutor399|0_400(StreamWriter tempLogSw, StreamWriter statsCsvSw, <>c__DisplayClass0_0&)

@v-wenyuxu
Copy link
Author

Failed in: runtime-coreclr r2r-extra 20250216.1

Failed tests:

R2R-CG2 windows x64 Checked jitstressregs8 @ Windows.10.Amd64.Open
    - JIT/IL_Conformance/Convert/TestConvertFromIntegral/TestConvertFromIntegral.dll

Error message:

 Assert.Equal() Failure: Values differ
Expected: 100
Actual:   101

Stack trace:

   at Xunit.Assert.Equal[T](T expected, T actual, IEqualityComparer`1 comparer) in /_/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs:line 174
   at Program.<<Main>$>g__TestExecutor399|0_400(StreamWriter tempLogSw, StreamWriter statsCsvSw, <>c__DisplayClass0_0&)

@amanasifkhalid
Copy link
Member

This might be fixed by #112217. I would wait for that to go in before starting to work on a different fix.

Looks like that didn't fix it. Since this only reproduces with DOTNET_JitStressRegs=8, I suspect this is caused by LSRA's reverse caller-callee stress mode (i.e. LsraSelect::LSRA_SELECT_REVERSE_CALLER_CALLEE). The failure doesn't repro for me with #108799 reverted, so that seems to have caused this. Here's a reduced repro:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Reflection;
using System.Reflection.Emit;

class Program
{
    static int failedCount;

    const bool ExpectException = true;
    const bool DontExpectException = false;

    const bool UnspecifiedBehaviour = true;

    [MethodImpl(MethodImplOptions.NoInlining)]
    static void GenerateTest<F, T>(F from, OpCode fromOpcode, OpCode convOpcode, bool exceptionExpected, T expectedTo, bool undefined = false) where F : struct where T : struct, IEquatable<T>
    {
        bool checkResult = !exceptionExpected && !undefined;
        Debug.Assert(!exceptionExpected || !checkResult);
        Debug.Assert(checkResult || expectedTo.Equals(default(T)));

        Type[] args = Array.Empty<Type>(); // No args.
        Type returnType = typeof(T);
        string name = "DynamicConvertFrom" + typeof(F).FullName + "To" + typeof(T).FullName + from.ToString() + expectedTo.ToString() + "Op" + convOpcode.Name;
        DynamicMethod dm = new DynamicMethod(name, returnType, args);

        ILGenerator generator = dm.GetILGenerator();

        if (typeof(F) == typeof(int)) generator.Emit(fromOpcode, (int)(object)from);
        else if (typeof(F) == typeof(long)) generator.Emit(fromOpcode, (long)(object)from);
        else if (typeof(F) == typeof(nint)) generator.Emit(fromOpcode, (nint)(object)from);
        else if (typeof(F) == typeof(float)) generator.Emit(fromOpcode, (float)(object)from);
        else if (typeof(F) == typeof(double)) generator.Emit(fromOpcode, (double)(object)from);
        else
        {
            throw new NotSupportedException();
        }

        generator.Emit(convOpcode);
        generator.Emit(OpCodes.Ret);

        try
        {
            T res = (T)dm.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null);
            Console.WriteLine("Expected: " + expectedTo + " Actual: " + res);
            if (exceptionExpected)
            {
                failedCount++;
                Console.WriteLine("No exception in " + name);
            }
            if (checkResult && !expectedTo.Equals(res))
            {
                failedCount++;
                Console.WriteLine("Wrong result in " + name);
            }
        }
        catch
        {
            if (!exceptionExpected)
            {
                failedCount++;
                Console.WriteLine("Not expected exception in " + name);
            }
        }
    }
    public static void Main()
    {
        OpCode sourceOp = OpCodes.Ldc_R4;

            OpCode convOvf = OpCodes.Conv_Ovf_U1;
            GenerateTest<float, byte>(1F, sourceOp, convOvf, DontExpectException, 1);
            GenerateTest<float, byte>(1.9F, sourceOp, convOvf, DontExpectException, 1);
            GenerateTest<float, byte>(sbyte.MaxValue, sourceOp, convOvf, DontExpectException, (byte)sbyte.MaxValue);
            GenerateTest<float, byte>(byte.MaxValue, sourceOp, convOvf, DontExpectException, byte.MaxValue);
            GenerateTest<float, byte>(byte.MinValue, sourceOp, convOvf, DontExpectException, byte.MinValue);
            GenerateTest<float, byte>(long.MaxValue, sourceOp, convOvf, ExpectException, 0);
            GenerateTest<float, byte>(long.MinValue, sourceOp, convOvf, ExpectException, 0);
            GenerateTest<float, byte>(Single.NaN, sourceOp, convOvf, ExpectException, 0);


            OpCode convOvfUn = OpCodes.Conv_Ovf_U1_Un;
            GenerateTest<float, byte>(1F, sourceOp, convOvfUn, DontExpectException, 1); // BAD
            GenerateTest<float, byte>(2.2F, sourceOp, convOvfUn, DontExpectException, 2);
            GenerateTest<float, byte>(sbyte.MaxValue, sourceOp, convOvfUn, DontExpectException, (byte)sbyte.MaxValue);
            GenerateTest<float, byte>(byte.MaxValue, sourceOp, convOvfUn, DontExpectException, byte.MaxValue);
            GenerateTest<float, byte>(byte.MinValue, sourceOp, convOvfUn, DontExpectException, byte.MinValue);
    }
}

@DeepakRajendrakumaran
Copy link
Contributor

This might be fixed by #112217. I would wait for that to go in before starting to work on a different fix.

Looks like that didn't fix it. Since this only reproduces with DOTNET_JitStressRegs=8, I suspect this is caused by LSRA's reverse caller-callee stress mode (i.e. LsraSelect::LSRA_SELECT_REVERSE_CALLER_CALLEE). The failure doesn't repro for me with #108799 reverted, so that seems to have caused this. Here's a reduced repro:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Reflection;
using System.Reflection.Emit;

class Program
{
static int failedCount;

const bool ExpectException = true;
const bool DontExpectException = false;

const bool UnspecifiedBehaviour = true;

[MethodImpl(MethodImplOptions.NoInlining)]
static void GenerateTest<F, T>(F from, OpCode fromOpcode, OpCode convOpcode, bool exceptionExpected, T expectedTo, bool undefined = false) where F : struct where T : struct, IEquatable<T>
{
    bool checkResult = !exceptionExpected && !undefined;
    Debug.Assert(!exceptionExpected || !checkResult);
    Debug.Assert(checkResult || expectedTo.Equals(default(T)));

    Type[] args = Array.Empty<Type>(); // No args.
    Type returnType = typeof(T);
    string name = "DynamicConvertFrom" + typeof(F).FullName + "To" + typeof(T).FullName + from.ToString() + expectedTo.ToString() + "Op" + convOpcode.Name;
    DynamicMethod dm = new DynamicMethod(name, returnType, args);

    ILGenerator generator = dm.GetILGenerator();

    if (typeof(F) == typeof(int)) generator.Emit(fromOpcode, (int)(object)from);
    else if (typeof(F) == typeof(long)) generator.Emit(fromOpcode, (long)(object)from);
    else if (typeof(F) == typeof(nint)) generator.Emit(fromOpcode, (nint)(object)from);
    else if (typeof(F) == typeof(float)) generator.Emit(fromOpcode, (float)(object)from);
    else if (typeof(F) == typeof(double)) generator.Emit(fromOpcode, (double)(object)from);
    else
    {
        throw new NotSupportedException();
    }

    generator.Emit(convOpcode);
    generator.Emit(OpCodes.Ret);

    try
    {
        T res = (T)dm.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null);
        Console.WriteLine("Expected: " + expectedTo + " Actual: " + res);
        if (exceptionExpected)
        {
            failedCount++;
            Console.WriteLine("No exception in " + name);
        }
        if (checkResult && !expectedTo.Equals(res))
        {
            failedCount++;
            Console.WriteLine("Wrong result in " + name);
        }
    }
    catch
    {
        if (!exceptionExpected)
        {
            failedCount++;
            Console.WriteLine("Not expected exception in " + name);
        }
    }
}
public static void Main()
{
    OpCode sourceOp = OpCodes.Ldc_R4;

        OpCode convOvf = OpCodes.Conv_Ovf_U1;
        GenerateTest<float, byte>(1F, sourceOp, convOvf, DontExpectException, 1);
        GenerateTest<float, byte>(1.9F, sourceOp, convOvf, DontExpectException, 1);
        GenerateTest<float, byte>(sbyte.MaxValue, sourceOp, convOvf, DontExpectException, (byte)sbyte.MaxValue);
        GenerateTest<float, byte>(byte.MaxValue, sourceOp, convOvf, DontExpectException, byte.MaxValue);
        GenerateTest<float, byte>(byte.MinValue, sourceOp, convOvf, DontExpectException, byte.MinValue);
        GenerateTest<float, byte>(long.MaxValue, sourceOp, convOvf, ExpectException, 0);
        GenerateTest<float, byte>(long.MinValue, sourceOp, convOvf, ExpectException, 0);
        GenerateTest<float, byte>(Single.NaN, sourceOp, convOvf, ExpectException, 0);


        OpCode convOvfUn = OpCodes.Conv_Ovf_U1_Un;
        GenerateTest<float, byte>(1F, sourceOp, convOvfUn, DontExpectException, 1); // BAD
        GenerateTest<float, byte>(2.2F, sourceOp, convOvfUn, DontExpectException, 2);
        GenerateTest<float, byte>(sbyte.MaxValue, sourceOp, convOvfUn, DontExpectException, (byte)sbyte.MaxValue);
        GenerateTest<float, byte>(byte.MaxValue, sourceOp, convOvfUn, DontExpectException, byte.MaxValue);
        GenerateTest<float, byte>(byte.MinValue, sourceOp, convOvfUn, DontExpectException, byte.MinValue);
}

}

Hello. What's the best way to repro duce this locally? I did the following and don't see the failure
.\build.cmd clr+libs -rc Debug -lc release
.\src\tests\build.cmd crossgen2 debug x64 -priority=1 tree JIT/IL_Conformance /p:LibrariesConfiguration=Release
set Dotnet_TieredCompilation=0
set DOTNET_JitStressRegs=8
src\tests\run.cmd x64 debug

Image

It would be helpful to know the exact repro steps

`

@kunalspathak
Copy link
Member

you can just create a hello world app with code provided by @amanasifkhalid and run with those environment variables set.

@amanasifkhalid
Copy link
Member

@DeepakRajendrakumaran your method of building and running the IL conformance tests should work, too. Could you please try rebuilding them without passing the crossgen2 argument to src\tests\build.cmd?

@DeepakRajendrakumaran
Copy link
Contributor

you can just create a hello world app with code provided by @amanasifkhalid and run with those environment variables set.

Tried that. Didn't help

Image

@DeepakRajendrakumaran
Copy link
Contributor

@DeepakRajendrakumaran your method of building and running the IL conformance tests should work, too. Could you please try rebuilding them without passing the crossgen2 argument to src\tests\build.cmd?

Still passing. Do you happen to know which exact x86 machine you're using?

@amanasifkhalid
Copy link
Member

Still passing. Do you happen to know which exact x86 machine you're using?

I'm able to repro this on an AMD EPYC 7763.

@DeepakRajendrakumaran
Copy link
Contributor

Still passing. Do you happen to know which exact x86 machine you're using?

I'm able to repro this on an AMD EPYC 7763.

Thanks for the information. I'm not exactly sure what I'm missing.

Can you check this- https://godbolt.org/z/n4danr8a3 . I would have expected the fail to show up here as well.
The env variables are set in the overrides tab

Image

@saucecontrol
Copy link
Member

saucecontrol commented Feb 19, 2025

FYI, the overrides tab doesn't work on compiler explorer for C#. You have to pass environment variables in the compiler flags, like this: https://godbolt.org/z/KKE5fKvnY

But it doesn't repro there anyway because it's Linux, and this looks to be an issue with callee-saved regs on Windows.

Here's the relevant asm from Main on a failing run:

       mov      rbx, 0xD1FFAB1E      ; data for System.Reflection.Emit.OpCodes:Conv_Ovf_U1_Un
       mov      esi, dword ptr [rbx]
       mov      ebx, dword ptr [rbx+0x04]
       mov      dword ptr [rsp+0x30], esi
       mov      dword ptr [rsp+0x34], ebx
       mov      dword ptr [rsp+0x20], 1
       xor      ebx, ebx
       mov      dword ptr [rsp+0x28], ebx
       mov      rbx, qword ptr [rsp+0x38]
       mov      rdx, rbx
       mov      rbx, qword ptr [rsp+0x30]
       mov      r8, rbx
       vmovaps  xmm0, xmm6  ; <---------- problem here, xmm6 has bad value
       xor      ebx, ebx
       mov      r9d, ebx
       call     [Programz:GenerateTest[float,ubyte](float,System.Reflection.Emit.OpCode,System.Reflection.Emit.OpCode,ubyte,ubyte,ubyte)]

xmm6 was loaded with the 1.0f const at the beginning of the method

       vmovss   xmm6, dword ptr [reloc @RWD00]

but apparently got clobbered by one of the calls in between, leaving it with a zero value, and causing the dynamic compiled conversion method to compile to const 0.

; Assembly listing for method (dynamicClass):DynamicConvertFromSystem.SingleToSystem.Byte01Opconv.ovf.u1.un():ubyte (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX512 - Windows
; FullOpts code
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; Final local variable assignments
;
;# V00 OutArgs      [V00    ] (  1,  1   )  struct ( 0) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace" <Empty>
;
; Lcl frame size = 0

G_M41797_IG01:  ;; offset=0x0000
       push     rbx
						;; size=1 bbWeight=1 PerfScore 1.00
G_M41797_IG02:  ;; offset=0x0001
       xor      ebx, ebx
       mov      eax, ebx
						;; size=4 bbWeight=1 PerfScore 0.50
G_M41797_IG03:  ;; offset=0x0005
       pop      rbx
       ret      
						;; size=2 bbWeight=1 PerfScore 1.50

; Total bytes of code 7, prolog size 1, PerfScore 3.00, instruction count 5, allocated bytes for code 7 (MethodHash=4ba45cba) for method (dynamicClass):DynamicConvertFromSystem.SingleToSystem.Byte01Opconv.ovf.u1.un():ubyte (FullOpts)
; ============================================================

Resulting in this output:

Expected: 1 Actual: 0
Wrong result in DynamicConvertFromSystem.SingleToSystem.Byte01Opconv.ovf.u1.un

@DeepakRajendrakumaran
Copy link
Contributor

@saucecontrol Thank you for the above. This really helps. I went through my PR and went over any parts likely to touch ABI logic. The parts I touched were all dealing only with int callee saved. The logic used for float registers should in theory remain untouched. I am continuing to go through the code to see if anything got affected but meanwhile I would really appreciate it if you can write down the exact steps you did to reproduce this include building local repo, csproj for creating the test and commands used to build and execute it.

@saucecontrol
Copy link
Member

There was nothing special to the repro, I'm afraid. Just:

Core_Root build:

  1. build clr+libs -c release -rc checked
  2. src\tests\build x64 checked generatelayoutonly

Repro build:

  1. dotnet new console (using net9 SDK for this part)
  2. replace Program.cs with @amanasifkhalid's code from above
  3. dotnet build -c release

Run:

  1. set DOTNET_TieredCompilation=0 and set DOTNET_JitStressRegs=8
  2. corerun repro.dll

Running a build from c153833, I've gotten the above failure every time (a few times I've had a second failure as well).

Tested the following, all with the same results:

  • Zen5, Windows 11 24H2
  • Meteor Lake, Windows 11 24H2
  • Skylake, Windows 10 22H2

@saucecontrol
Copy link
Member

saucecontrol commented Feb 19, 2025

FWIW, I ran with JitDisasm=* and didn't see any JIT-compiled methods that used xmm6 without saving/restoring it. Could be something around managed<-->native transitions.

@saucecontrol
Copy link
Member

(a few times I've had a second failure as well)

Second failure reproduces reliably for me with DOTNET_ReadyToRun=0

Expected: 127 Actual: 0
Wrong result in DynamicConvertFromSystem.SingleToSystem.Byte0127Opconv.ovf.u1.un

@DeepakRajendrakumaran
Copy link
Contributor

  1. set DOTNET_JitStressRegs=8

I'm able to repro now. I should be able to figure it out from here :)

Thank you. Appreciate the help

@DeepakRajendrakumaran
Copy link
Contributor

DeepakRajendrakumaran commented Feb 20, 2025

A simplified repro.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Reflection;
using System.Reflection.Emit;

class Program
{

    
    [MethodImpl(MethodImplOptions.NoInlining)]
    static int GenerateTest(float from, OpCode fromOpcode, OpCode convOpcode)
    {


        Type[] args = Array.Empty<Type>(); // No args.

        Type returnType = typeof(byte);
        string name = "DynamicConvertFromSystem.SingleToSystem.Byte";
        DynamicMethod dm = new DynamicMethod(name, returnType, args);

        ILGenerator generator = dm.GetILGenerator();
        generator.Emit(fromOpcode, (float)(object)from);


        generator.Emit(convOpcode);
        generator.Emit(OpCodes.Ret);

        try
        {
            byte res = (byte)dm.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null);
            return res;

        }
        catch
        {

        }
        return -1;
    }

    public static void Main()
    {
        OpCode sourceOp = OpCodes.Ldc_R4;

        OpCode convOvf = OpCodes.Conv_Ovf_U1;
        float inVal = 1F; // vmovss   xmm6, dword ptr [reloc @RWD00]
        GenerateTest(inVal, sourceOp, convOvf);
        Console.WriteLine("1 inVal: " + inVal);// inVal is still good
        GenerateTest(Single.NaN, sourceOp, convOvf); // This overwrites xmm6
        Console.WriteLine("2 inVal: " + inVal);// inVal has junk value now 


        OpCode convOvfUn = OpCodes.Conv_Ovf_U1_Un;
        GenerateTest(inVal, sourceOp, convOvfUn); // BAD
    }
}

@DeepakRajendrakumaran
Copy link
Contributor

DeepakRajendrakumaran commented Feb 21, 2025

@kunalspathak identified the issue and has a PR - #112799

This is due to a bug in the unwind code not working correctly due to change is regnum as a result of newly added registers.

In the example above, GenerateTest(Single.NaN, sourceOp, convOvf); throws an exception.

Internally, GenerateTest(Single.NaN, sourceOp, convOvf); creates a dynamic method and invokes it. For the call with Single.NaN, this looks as follows

; Assembly listing for method (dynamicClass):foo():ubyte (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX512 - Windows
; FullOpts code
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; Final local variable assignments
;
;  V00 OutArgs      [V00    ] (  1,  1   )  struct (32) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace" <UNNAMED>
;
; Lcl frame size = 48

G_M46840_IG01:  ;; offset=0x0000
       push     rbx
       sub      rsp, 48
       vmovaps  xmmword ptr [rsp+0x20], xmm6
						;; size=11 bbWeight=1 PerfScore 3.25
G_M46840_IG02:  ;; offset=0x000B
       vmovsd   xmm6, qword ptr [reloc @RWD00]
       vmovaps  xmm0, xmm6
       call     [CORINFO_HELP_DBL2INT_OVF]
       mov      ebx, eax
       cmp      ebx, 255
       ja       SHORT G_M46840_IG04
       mov      eax, ebx
						;; size=30 bbWeight=1 PerfScore 8.00
G_M46840_IG03:  ;; offset=0x0029
       vmovaps  xmm6, xmmword ptr [rsp+0x20]
       add      rsp, 48
       pop      rbx
       ret      
						;; size=12 bbWeight=1 PerfScore 5.75
G_M46840_IG04:  ;; offset=0x0035
       call     CORINFO_HELP_OVERFLOW
       int3     
						;; size=6 bbWeight=0 PerfScore 0.00
RWD00  	dq	FFF8000000000000h	;    -nan(ind)


; Total bytes of code 59, prolog size 11, PerfScore 17.00, instruction count 16, allocated bytes for code 59 (MethodHash=965e4907) for method (dynamicClass):foo():ubyte (FullOpts)
; ============================================================

Unwind Info:
  >> Start offset   : 0x000000 (not in unwind data)
  >>   End offset   : 0x00003b (not in unwind data)
  Version           : 1
  Flags             : 0x00
  SizeOfProlog      : 0x0B
  CountOfUnwindCodes: 4
  FrameRegister     : none (0)
  FrameOffset       : N/A (no FrameRegister) (Value=0)
  UnwindCodes       :
    CodeOffset: 0x0B UnwindOp: UWOP_SAVE_XMM128 (8)     OpInfo: XMM14 (14)
      Scaled Small Offset: 2 * 16 = 32 = 0x00020
    CodeOffset: 0x05 UnwindOp: UWOP_ALLOC_SMALL (2)     OpInfo: 5 * 8 + 8 = 48 = 0x30
    CodeOffset: 0x01 UnwindOp: UWOP_PUSH_NONVOL (0)     OpInfo: rbx (3)

Here, we can see that we are handling xmm6 in the function epilog/prolog but the unwind code identifies xmm6 as xmm14
The unwind code with/without my PR

Image

@kunalspathak
Copy link
Member

Just to elaborate, xmm6 is the one that we are storing on stack in prolog, but in unwind, it is registering xmm14. So when exception happens, it will restore the value to xmm14 instead of xmm6.

Image

@kunalspathak
Copy link
Member

fixed by #112799

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-x64 area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI blocking-clean-ci-optional Blocking optional rolling runs os-windows
Projects
None yet
Development

No branches or pull requests

6 participants