35

After recently upgrading to .net 4.6 we discovered a bug where RyuJit produces incorrect results, we were able to work around the issue for now by adding useLegacyJit enabled="true" to the app.config.

How can I debug the machine code generated by the following?

I created a new console project in VS 2015 RTM, set to Release, Any CPU, unchecked Prefer 32 bit, running with and without debugger attached produces the same result.

using System;
using System.Runtime.CompilerServices;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Calculate());
            Console.WriteLine(Calculate());

            Console.ReadLine();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Value Calculate()
        {
            bool? _0 = (bool?)null;
            bool? _1 = (bool?)true;
            if (!Value.IsPresent<bool>(_1))
            {
                return default(Value);
            }

            bool? result = null;
            result = (_1.Value ? new bool?(false) : result);
            if (_0.HasValue && _0.Value)
            {
            }
            return new Value(result);
        }

        public struct Value
        {
            bool? _value;

            public Value(bool? value)
            {
                _value = value;
            }

            public static bool IsPresent<T>(bool? _)
            {
                return _.HasValue;
            }

            public override string ToString()
            {
                return _value.ToString();
            }
        }
    }
}

It should produce: False False

but instead it produces: True False

The key part of the example is

result = true ? false : result;

Which should always return false, but as you can see from the output, it returns True the first time the method is run, and a different answer the second time the method is run. Removing some more lines from the Calculate() method will cause it to return True always, but the example given is the closest I could reproduce to our actual production scenario.

BrandonAGr
  • 5,827
  • 5
  • 47
  • 72
  • 5
    Yes, it is an inlining optimization bug. The first inlined Calculate() has a bad instruction. Looks to me there's a `!` somewhere inside the optimizer that shouldn't be there. Nothing we can do to fix this bug of course, you can report it at connect.microsoft.com. Just lay off the MethodImplOptions.AggressiveInlining for a while, that is unlikely to have been extensively tested. – Hans Passant Jul 22 '15 at 18:26
  • The actual production code does not have the AggressiveInlining attribute, but that is the only way I could reproduce the behavior in this small example. The production code is being partially machine generated, which is why the example code looks a bit odd. I just reported it at https://connect.microsoft.com/VisualStudio/feedback/details/1578173 – BrandonAGr Jul 22 '15 at 18:31
  • @BrandonAGr This bug is caused when inlining? Using `MethodImplOptions.NoInlining` is a possible work around? – Ortiga Jul 22 '15 at 20:09
  • 2
    I have reported the bug to the coreclr repo: https://github.com/dotnet/coreclr/issues/1299 In the issue, you can find a simplified version of your code with asm listings. – AndreyAkinshin Jul 27 '15 at 20:04
  • Seems the same as http://stackoverflow.com/questions/17483585/recursion-in-windows-7-64-bit – Bruno Canettieri Jul 29 '15 at 15:56

1 Answers1

27

Thank you for the isolated repro program and I can confirm that this is indeed a RyuJIT bug in the optimizer which got exposed due to inlining. I've made a fix to the compiler and figuring the roll out details. Not to turn SO into a bug tracker, and for a quicker turnaround: ryujit@microsoft.com.

schellap
  • 371
  • 3
  • 3
  • 3
    I have build a simplified version of the source code, you can find it in the coreclr repo: https://github.com/dotnet/coreclr/issues/1299 – AndreyAkinshin Jul 27 '15 at 20:06
  • 1
    @HansPassant Yeah wasn't intending this as a bug report, the kind of hidden question was how to find the machine code being executed. The simplified example Andrey produced made it very easy to just view the disassembly in visual studio, of course it is still not easy to realize what registers are holding what but I was able to walk through it – BrandonAGr Jul 27 '15 at 20:51
  • @schellap Does this fix also fix - https://connect.microsoft.com/VisualStudio/Feedback/Details/1602437 ? – Stephen Gennard Aug 04 '15 at 00:03