Vector3.Cross calculates wrong value
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 0 0> vector2 <0 1 0>
// cross <0 0 0>
// crossAlt <0 0 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)
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 0 0> vector2 <0 1 0>
// cross <0 0 0>
// crossAlt <0 0 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: |
|
| Milestone: | - |
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);
}
}
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.
Is this one that should be backported to .NET 6? CC. @BruceForstall
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.
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 0 0> vector2 <0 1 0>
// cross <0 0 0>
// crossAlt <0 0 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: |
|
| Milestone: | - |
@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)
Fixed by https://github.com/dotnet/runtime/pull/73654. Will be in a subsequent .NET 6 update.