Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this a possible bug in .Net Native compilation and optimization?

I discovered an issue with (what might be) over-optimization in .Net Native and structs. I'm not sure if the compiler is too aggressive, or I'm too blind to see what I've done wrong.

To reproduce this, follow these steps:

Step 1: Create a new Blank Universal (win10) app in Visual Studio 2015 Update 2 targeting build 10586 with a min build of 10240. Call the project NativeBug so we have the same namespace.

Step 2: Open MainPage.xaml and insert this label

<Page x:Class="NativeBug.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <!-- INSERT THIS LABEL -->
        <TextBlock x:Name="_Label" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</Page>

Step 3: Copy/paste the following into MainPage.xaml.cs

using System;
using System.Collections.Generic;

namespace NativeBug
{
    public sealed partial class MainPage
    {
        public MainPage()
        {
            InitializeComponent();

            var startPoint = new Point2D(50, 50);
            var points = new[]
            {
                new Point2D(100, 100), 
                new Point2D(100, 50), 
                new Point2D(50, 100), 
            };

            var bounds = ComputeBounds(startPoint, points, 15);

            _Label.Text = $"{bounds.MinX} , {bounds.MinY}   =>   {bounds.MaxX} , {bounds.MaxY}";
        }

        private static Rectangle2D ComputeBounds(Point2D startPoint, IEnumerable<Point2D> points, double strokeThickness = 0)
        {
            var lastPoint = startPoint;
            var cumulativeBounds = new Rectangle2D();

            foreach (var point in points)
            {
                var bounds = ComputeBounds(lastPoint, point, strokeThickness);
                cumulativeBounds = cumulativeBounds.Union(bounds);
                lastPoint = point;
            }

            return cumulativeBounds;
        }

        private static Rectangle2D ComputeBounds(Point2D fromPoint, Point2D toPoint, double strokeThickness)
        {
            var bounds = new Rectangle2D(fromPoint.X, fromPoint.Y, toPoint.X, toPoint.Y);

            // ** Uncomment the line below to see the difference **
            //return strokeThickness <= 0 ? bounds : bounds.Inflate2(strokeThickness);

            return strokeThickness <= 0 ? bounds : bounds.Inflate1(strokeThickness);
        }
    }

    public struct Point2D
    {
        public readonly double X;
        public readonly double Y;

        public Point2D(double x, double y)
        {
            X = x;
            Y = y;
        }
    }

    public struct Rectangle2D
    {
        public readonly double MinX;
        public readonly double MinY;
        public readonly double MaxX;
        public readonly double MaxY;

        private bool IsEmpty => MinX == 0 && MinY == 0 && MaxX == 0 && MaxY == 0;

        public Rectangle2D(double x1, double y1, double x2, double y2)
        {
            MinX = Math.Min(x1, x2);
            MinY = Math.Min(y1, y2);
            MaxX = Math.Max(x1, x2);
            MaxY = Math.Max(y1, y2);
        }

        public Rectangle2D Union(Rectangle2D rectangle)
        {
            if (IsEmpty)
            {
                return rectangle;
            }

            var newMinX = Math.Min(MinX, rectangle.MinX);
            var newMinY = Math.Min(MinY, rectangle.MinY);
            var newMaxX = Math.Max(MaxX, rectangle.MaxX);
            var newMaxY = Math.Max(MaxY, rectangle.MaxY);

            return new Rectangle2D(newMinX, newMinY, newMaxX, newMaxY);
        }

        public Rectangle2D Inflate1(double value)
        {
            var halfValue = value * .5;

            return new Rectangle2D(MinX - halfValue, MinY - halfValue, MaxX + halfValue, MaxY + halfValue);
        }

        public Rectangle2D Inflate2(double value)
        {
            var halfValue = value * .5;
            var x1 = MinX - halfValue;
            var y1 = MinY - halfValue;
            var x2 = MaxX + halfValue;
            var y2 = MaxY + halfValue;

            return new Rectangle2D(x1, y1, x2, y2);
        }
    }
}

Step 4: Run the application in Debug x64. You should see this label:

42.5 , 42.5 => 107.5 , 107.5

Step 5: Run the application in Release x64. You should see this label:

-7.5 , -7.5 => 7.5, 7.5

Step 6: Uncomment line 45 in MainPage.xaml.cs and repeat step 5. Now you see the original label

42.5 , 42.5 => 107.5 , 107.5


By commenting out line 45, the code will use Rectangle2D.Inflate2(...) which is exactly the same as Rectangle2D.Inflate1(...) except it creates a local copy of the computations before sending them to the constructor of Rectangle2D. In debug mode, these two function exactly the same. In release however, something is getting optimized out.

This was a nasty bug in our app. The code you see here was stripped from a much larger library and I'm afraid there might be more. Before I report this to Microsoft, I would appreciate it if you could take a look and let me know why Inflate1 doesn't work in release mode. Why do we have to create local copies?

like image 464
Laith Avatar asked Jun 19 '16 14:06

Laith


People also ask

Can C# be compiled to native code?

NET Native is a precompilation technology for building and deploying UWP apps. . NET Native is included with Visual Studio 2015 and later versions. It automatically compiles the release version of UWP apps that are written in managed code (C# or Visual Basic) to native code.

What compiler does .NET use?

. NET uses two compilers, Roslyn, to compile C# or VB code into CIL (common intermediate language), and RyuJIT, to run just-in-time compilation of CIL into native code.

What is Microsoft net Native framework?

NET Framework is a software development framework for building and running applications on Windows. . NET Framework is part of the . NET platform, a collection of technologies for building apps for Linux, macOS, Windows, iOS, Android, and more.

Is .NET code compiled?

NET Framework are written in a particular programming language and compiled into intermediate language (IL). At run time, a just-in-time (JIT) compiler is responsible for compiling the IL into native code for the local machine just before a method is executed for the first time.

Is .NET 7 fast?

NET 7 is fast. Really fast. A thousand performance-impacting PRs went into runtime and core libraries this release, never mind all the improvements in ASP.NET Core and Windows Forms and Entity Framework and beyond. It's the fastest .


1 Answers

Pretty unclear to me why this question has a bounty. Yes, it is a bug as @Matt told you. He knows, he works on .NET Native. And he documented the temporary workaround, use an attribute to prevent the method from getting inlined by the optimizer. A trick that often works to sail around optimizer bugs.

using System.Runtime.CompilerServices;
....
    [MethodImpl(MethodImplOptions.NoInlining)]
    public Rectangle2D Inflate1(double value)
    {
        // etc...
    }

They'll get it fixed, next major release is the usual promise.

like image 183
Hans Passant Avatar answered Oct 24 '22 20:10

Hans Passant