Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# 64 bit release build started without debugging behaves differently to when started with debugging (BigInteger)

I'm new to C# and have run into a problem with the following code (I have the target framework as 4.5 and I've added a reference to System.Numerics):

using System;
using System.Numerics;

namespace Test
{
    class Program
    {
        static BigInteger Gcd(BigInteger x, BigInteger y)
        {
            Console.WriteLine("GCD {0}, {1}", x, y);
            if (x < y) return Gcd(y, x);
            if (x % y == 0) return y;
            return Gcd(y, x % y);
        }

        static void Main(string[] args)
        {
            BigInteger a = 13394673;
            BigInteger b = 53578691;
            Gcd(a, b);
        }
    }
}

When the release build is started with debugging (F5 in Visual Studio - and a break-point at the end of program so I can see the output), I get the following output:

GCD 13394673, 53578691
GCD 53578691, 13394673
GCD 13394673, 13394672
GCD 13394672, 1

However, when the release build is started without debugging (Ctrl-F5), I get the following:

GCD 13394673, 53578691
GCD 53578691, 53578691

Strangely if I add a Console.ReadLine() at the end of the program it works as expected!

Any ideas what is causing this? Thanks.

like image 447
Phil Sturges Avatar asked Jun 30 '15 11:06

Phil Sturges


1 Answers

This is an x64 jitter optimizer bug in .NET 4.0 through 4.5.2. Characterizing it is fairly difficult, the codegen is pretty heavy due to the BigInteger usage. The x64 jitter has had a history of optimizer bugs for struct types, like BigInteger, so that's the probable underlying cause. The combination with the possible tail-call optimization in this method is the most likely trigger.

I would normally recommend reporting such a bug, but the days of this jitter are numbered. Microsoft decided to retire it and rewrite it completely. Available in .NET 4.6 - VS2015, the project code name was RyuJIT. It doesn't have this bug.

Several possible workarounds:

Project + Properties, Build tab, Platform target = x86. That forces the program to run in 32-bit mode and use the x86 jitter, it doesn't have this bug.

Or disable optimization for this method with an attribute:

  using System.Runtime.CompilerServices;
  ...
    [MethodImpl(MethodImplOptions.NoOptimization)]
    static BigInteger Gcd(BigInteger x, BigInteger y) {
        // etc...
    }

Which is fine, the heavy lifting is in the BigInteger class so disabling the optimization isn't going to affect execution time.

like image 67
Hans Passant Avatar answered Oct 13 '22 14:10

Hans Passant