runtime icon indicating copy to clipboard operation
runtime copied to clipboard

Vector3.Cross calculates wrong value

Open vsfeedback opened this issue 3 years ago • 8 comments

This issue has been moved from a ticket on Developer Community.


Using .net 6.0 and build release mode, 64-bit, in VS2022. The calculation of Vector3.Cross fails for no obvious reason. I also made my own Cross method (copy calculations from Vector3.Cross) and it too behaves strange.

I found the Cross result was affected by seemingly unrelated lines of code, and changing those unrelated lines makes it work.

In the Main method

  • a LINQ expression on a dummy variable
  • a dummy loop
  • passing vectors created from variables instead of constants In my CrossAlt method:
  • WriteLine of the input vectors
  • WriteLine of temp crossAlt.Z
  • Test if crossAlt.Z is zero and throw exception

I have seen similar strange behaviour with Vector3.Dot and using .net5.0 and VS2019. Also 64-bit release mode builds. Then fixed it by "modifying unrelated lines until it worked". Never seen it in debug mode.

This makes me think it's a bug in the release-mode optimization for 64-bit code in .net 6.0 (and 5.0).

namespace CrossBug
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Numerics;

// Cross is calculated wrong. Sample output:
    // vector1 <1&nbsp; 0&nbsp; 0> vector2 <0&nbsp; 1&nbsp; 0>
    // cross <0&nbsp; 0&nbsp; 0>
    // crossAlt <0&nbsp; 0&nbsp; 0>
    //
    // Expected <0 0 1>
    // Change any of the places commented "Cross wrong" to get a correct value.

internal class Program
    {
        public static void Main(string[] args)
        {
            Matrix4x4 indexToWorld = Matrix4x4.Identity;
            List<(short, short, short)> dummy = new() { (0, 0, 0) };

// Cross wrong if keeping next. Comment out => cross ok.
            IEnumerable<float> radiusList = dummy. Select(
                c => Vector3.Transform(new Vector3(0, 0, 0), indexToWorld). Length());

// Cross wrong if keeping dummy loop. Comment out => cross ok.
            for (int z = -1; z <= 1; z++)
            {
            }

// Cross wrong if using indexToWorld. Change to constants => cross ok.
            var vector1 = new Vector3(indexToWorld.M11, indexToWorld.M12, indexToWorld.M13);
            var vector2 = new Vector3(indexToWorld.M21, indexToWorld.M22, indexToWorld.M23);

// Cross ok with constants
            //var vector1 = new Vector3(1, 0, 0);
            //var vector2 = new Vector3(0, 1, 0);
            
Console.WriteLine($"vector1 {vector1} vector2 {vector2}");
            var cross = Vector3.Cross(vector1, vector2);
            Console.WriteLine($"cross {cross}");

var crossAlt = CrossAlt(vector1, vector2);
            Console.WriteLine($"crossAlt {crossAlt}");
        }

public static Vector3 CrossAlt(Vector3 vector1, Vector3 vector2)
        {
            // Cross wrong if commented out. Uncomment WriteLine => cross ok.
            //Console.WriteLine($"vector1 {vector1} vector2 {vector2}");

// Same calculation as Vector3.Cross
            var crossAlt = new Vector3(
                (vector1. Y * vector2. Z) - (vector1. Z * vector2. Y),
                (vector1. Z * vector2. X) - (vector1. X * vector2. Z),
                (vector1. X * vector2. Y) - (vector1. Y * vector2. X));

// Cross wrong if comment out. Uncomment WriteLine => cross ok. (prints Z=1).
            //Console.WriteLine($"z {crossAlt.Z}");

if (crossAlt.Z == 0)
            {
                // Cross wrong if comment out. Uncomment the throw => cross ok (so Z=1)
                //throw new Exception("Dummy");
            }

return crossAlt;
        }
    }
}

Original Comments

Feedback Bot on 1/17/2022, 08:51 AM:

(private comment, text removed)

Lars Bjornfot on 1/20/2022, 03:21 PM:

(private comment, text removed)

Feedback Bot on 5/7/2022, 11:07 AM:

(private comment, text removed)


Original Solutions

(no solutions)

vsfeedback avatar Jun 07 '22 19:06 vsfeedback

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

Tagging subscribers to this area: @dotnet/area-system-numerics See info in area-owners.md if you want to be subscribed.

Issue Details

This issue has been moved from a ticket on Developer Community.


Using .net 6.0 and build release mode, 64-bit, in VS2022. The calculation of Vector3.Cross fails for no obvious reason. I also made my own Cross method (copy calculations from Vector3.Cross) and it too behaves strange.

I found the Cross result was affected by seemingly unrelated lines of code, and changing those unrelated lines makes it work.

In the Main method

  • a LINQ expression on a dummy variable
  • a dummy loop
  • passing vectors created from variables instead of constants In my CrossAlt method:
  • WriteLine of the input vectors
  • WriteLine of temp crossAlt.Z
  • Test if crossAlt.Z is zero and throw exception

I have seen similar strange behaviour with Vector3.Dot and using .net5.0 and VS2019. Also 64-bit release mode builds. Then fixed it by "modifying unrelated lines until it worked". Never seen it in debug mode.

This makes me think it's a bug in the release-mode optimization for 64-bit code in .net 6.0 (and 5.0).

namespace CrossBug
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Numerics;

// Cross is calculated wrong. Sample output:
    // vector1 <1&nbsp; 0&nbsp; 0> vector2 <0&nbsp; 1&nbsp; 0>
    // cross <0&nbsp; 0&nbsp; 0>
    // crossAlt <0&nbsp; 0&nbsp; 0>
    //
    // Expected <0 0 1>
    // Change any of the places commented "Cross wrong" to get a correct value.

internal class Program
    {
        public static void Main(string[] args)
        {
            Matrix4x4 indexToWorld = Matrix4x4.Identity;
            List<(short, short, short)> dummy = new() { (0, 0, 0) };

// Cross wrong if keeping next. Comment out => cross ok.
            IEnumerable<float> radiusList = dummy. Select(
                c => Vector3.Transform(new Vector3(0, 0, 0), indexToWorld). Length());

// Cross wrong if keeping dummy loop. Comment out => cross ok.
            for (int z = -1; z <= 1; z++)
            {
            }

// Cross wrong if using indexToWorld. Change to constants => cross ok.
            var vector1 = new Vector3(indexToWorld.M11, indexToWorld.M12, indexToWorld.M13);
            var vector2 = new Vector3(indexToWorld.M21, indexToWorld.M22, indexToWorld.M23);

// Cross ok with constants
            //var vector1 = new Vector3(1, 0, 0);
            //var vector2 = new Vector3(0, 1, 0);
            
Console.WriteLine($"vector1 {vector1} vector2 {vector2}");
            var cross = Vector3.Cross(vector1, vector2);
            Console.WriteLine($"cross {cross}");

var crossAlt = CrossAlt(vector1, vector2);
            Console.WriteLine($"crossAlt {crossAlt}");
        }

public static Vector3 CrossAlt(Vector3 vector1, Vector3 vector2)
        {
            // Cross wrong if commented out. Uncomment WriteLine => cross ok.
            //Console.WriteLine($"vector1 {vector1} vector2 {vector2}");

// Same calculation as Vector3.Cross
            var crossAlt = new Vector3(
                (vector1. Y * vector2. Z) - (vector1. Z * vector2. Y),
                (vector1. Z * vector2. X) - (vector1. X * vector2. Z),
                (vector1. X * vector2. Y) - (vector1. Y * vector2. X));

// Cross wrong if comment out. Uncomment WriteLine => cross ok. (prints Z=1).
            //Console.WriteLine($"z {crossAlt.Z}");

if (crossAlt.Z == 0)
            {
                // Cross wrong if comment out. Uncomment the throw => cross ok (so Z=1)
                //throw new Exception("Dummy");
            }

return crossAlt;
        }
    }
}

Original Comments

Feedback Bot on 1/17/2022, 08:51 AM:

(private comment, text removed)

Lars Bjornfot on 1/20/2022, 03:21 PM:

(private comment, text removed)

Feedback Bot on 5/7/2022, 11:07 AM:

(private comment, text removed)


Original Solutions

(no solutions)

Author: vsfeedback
Assignees: -
Labels:

area-System.Numerics, untriaged

Milestone: -

msftbot[bot] avatar Jun 07 '22 19:06 msftbot[bot]

Additional comment from user:

My current workaround is to use this class instead when Vector3.Cross is wrong.

        public class Vector3Workaround
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static Vector3 Cross(Vector3 vector1, Vector3 vector2)
            {
                return Vector3.Cross(vector1, vector2);
            }
        }

CarnaViire avatar Jun 07 '22 19:06 CarnaViire

This is probably related to or the same as https://github.com/dotnet/runtime/issues/64375 which was fixed by @SingleAccretion in https://github.com/dotnet/runtime/pull/69992.

tannergooding avatar Jun 07 '22 19:06 tannergooding

Is this one that should be backported to .NET 6? CC. @BruceForstall

tannergooding avatar Jun 07 '22 19:06 tannergooding

Being the author of both tickets, I can confirm that #64375 is the same issue. And we would be very happy to see it backported to NET-6.0.

larsbjornfot avatar Jun 09 '22 15:06 larsbjornfot

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

Issue Details

This issue has been moved from a ticket on Developer Community.


Using .net 6.0 and build release mode, 64-bit, in VS2022. The calculation of Vector3.Cross fails for no obvious reason. I also made my own Cross method (copy calculations from Vector3.Cross) and it too behaves strange.

I found the Cross result was affected by seemingly unrelated lines of code, and changing those unrelated lines makes it work.

In the Main method

  • a LINQ expression on a dummy variable
  • a dummy loop
  • passing vectors created from variables instead of constants In my CrossAlt method:
  • WriteLine of the input vectors
  • WriteLine of temp crossAlt.Z
  • Test if crossAlt.Z is zero and throw exception

I have seen similar strange behaviour with Vector3.Dot and using .net5.0 and VS2019. Also 64-bit release mode builds. Then fixed it by "modifying unrelated lines until it worked". Never seen it in debug mode.

This makes me think it's a bug in the release-mode optimization for 64-bit code in .net 6.0 (and 5.0).

namespace CrossBug
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Numerics;

// Cross is calculated wrong. Sample output:
    // vector1 <1&nbsp; 0&nbsp; 0> vector2 <0&nbsp; 1&nbsp; 0>
    // cross <0&nbsp; 0&nbsp; 0>
    // crossAlt <0&nbsp; 0&nbsp; 0>
    //
    // Expected <0 0 1>
    // Change any of the places commented "Cross wrong" to get a correct value.

internal class Program
    {
        public static void Main(string[] args)
        {
            Matrix4x4 indexToWorld = Matrix4x4.Identity;
            List<(short, short, short)> dummy = new() { (0, 0, 0) };

// Cross wrong if keeping next. Comment out => cross ok.
            IEnumerable<float> radiusList = dummy. Select(
                c => Vector3.Transform(new Vector3(0, 0, 0), indexToWorld). Length());

// Cross wrong if keeping dummy loop. Comment out => cross ok.
            for (int z = -1; z <= 1; z++)
            {
            }

// Cross wrong if using indexToWorld. Change to constants => cross ok.
            var vector1 = new Vector3(indexToWorld.M11, indexToWorld.M12, indexToWorld.M13);
            var vector2 = new Vector3(indexToWorld.M21, indexToWorld.M22, indexToWorld.M23);

// Cross ok with constants
            //var vector1 = new Vector3(1, 0, 0);
            //var vector2 = new Vector3(0, 1, 0);
            
Console.WriteLine($"vector1 {vector1} vector2 {vector2}");
            var cross = Vector3.Cross(vector1, vector2);
            Console.WriteLine($"cross {cross}");

var crossAlt = CrossAlt(vector1, vector2);
            Console.WriteLine($"crossAlt {crossAlt}");
        }

public static Vector3 CrossAlt(Vector3 vector1, Vector3 vector2)
        {
            // Cross wrong if commented out. Uncomment WriteLine => cross ok.
            //Console.WriteLine($"vector1 {vector1} vector2 {vector2}");

// Same calculation as Vector3.Cross
            var crossAlt = new Vector3(
                (vector1. Y * vector2. Z) - (vector1. Z * vector2. Y),
                (vector1. Z * vector2. X) - (vector1. X * vector2. Z),
                (vector1. X * vector2. Y) - (vector1. Y * vector2. X));

// Cross wrong if comment out. Uncomment WriteLine => cross ok. (prints Z=1).
            //Console.WriteLine($"z {crossAlt.Z}");

if (crossAlt.Z == 0)
            {
                // Cross wrong if comment out. Uncomment the throw => cross ok (so Z=1)
                //throw new Exception("Dummy");
            }

return crossAlt;
        }
    }
}

Original Comments

Feedback Bot on 1/17/2022, 08:51 AM:

(private comment, text removed)

Lars Bjornfot on 1/20/2022, 03:21 PM:

(private comment, text removed)

Feedback Bot on 5/7/2022, 11:07 AM:

(private comment, text removed)


Original Solutions

(no solutions)

Author: vsfeedback
Assignees: -
Labels:

area-CodeGen-coreclr

Milestone: -

msftbot[bot] avatar Jun 23 '22 23:06 msftbot[bot]

@SingleAccretion I notice your test https://github.com/dotnet/runtime/issues/64375#issuecomment-1023234701 doesn't fail in .NET 6, but Egor's test https://github.com/dotnet/runtime/issues/64375#issuecomment-1023199708 does. Did we port anything that would fix one but not the other to 6?

@BruceForstall Nothing comes to my mind in terms of backported changes. It is possible/probable that my sample relied on some 7-only change, though I couldn't think of which one exactly.

(Replying here because #64375 is locked)

SingleAccretion avatar Aug 06 '22 14:08 SingleAccretion

Fixed by https://github.com/dotnet/runtime/pull/73654. Will be in a subsequent .NET 6 update.

BruceForstall avatar Aug 11 '22 21:08 BruceForstall