When >> on a vector has to be emulated, the JIT evaluates the shift-count operand before the vector operand, so the implicit exception checks of the two operands fire in the wrong order.
Minimal Repro
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
public class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
static Vector128<sbyte> Test(Vector128<sbyte>[] a, int[] b) => a[0] >> b[0];
[MethodImpl(MethodImplOptions.NoInlining)]
static Vector128<int> Control(Vector128<int>[] a, int[] b) => a[0] >> b[0];
public static void Main()
{
try { Test(null!, Array.Empty<int>()); } catch (Exception e) { Console.WriteLine("sbyte: " + e.GetType().Name); }
try { Control(null!, Array.Empty<int>()); } catch (Exception e) { Console.WriteLine("int : " + e.GetType().Name); }
}
}
Expected
sbyte: NullReferenceException
int : NullReferenceException
Actual
sbyte: IndexOutOfRangeException
int : NullReferenceException
Notes
Repros on released .NET 10.0.12, and also under DOTNET_JITMinOpts=1, so it is not optimizer-dependent.
sbyte is always emulated; Vector128<long>/Vector256<long> show the same reversal once AVX-512 is unavailable (DOTNET_EnableAVX512=0). short/int use a native psra* and are correct.
Only implicit checks reorder: side effects expressed as calls are spilled to temps in source order and stay correct.
gtNewSimdBinOpNode GT_RSH (gentree.cpp) puts the fgMakeMultiUse definition of the non-constant shift count into maskOp, which becomes the first operand of the ConditionalSelect, while the vector operand stays second.
When
>>on a vector has to be emulated, the JIT evaluates the shift-count operand before the vector operand, so the implicit exception checks of the two operands fire in the wrong order.Minimal Repro
Expected
Actual
Notes
Repros on released .NET 10.0.12, and also under
DOTNET_JITMinOpts=1, so it is not optimizer-dependent.sbyteis always emulated;Vector128<long>/Vector256<long>show the same reversal once AVX-512 is unavailable (DOTNET_EnableAVX512=0).short/intuse a nativepsra*and are correct.Only implicit checks reorder: side effects expressed as calls are spilled to temps in source order and stay correct.
gtNewSimdBinOpNodeGT_RSH (gentree.cpp) puts thefgMakeMultiUsedefinition of the non-constant shift count intomaskOp, which becomes the first operand of theConditionalSelect, while the vector operand stays second.