fgOperIsBitwiseRotationRoot accepts GT_XOR as a rotation root, but OR and XOR are only interchangeable when
the two shifted values have disjoint bits. With a variable count that is a multiple of the operand width both
sub-shifts equal x, so the XOR must be 0 while ROL(x, 0) is x.
Minimal Repro
using System;
using System.Runtime.CompilerServices;
public class Program
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static int Test(int x, int y) => (x << (y & 31)) ^ (int)((uint)x >>> ((32 - y) & 31));
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static long TestLong(long x, int y) => (x << (y & 63)) ^ (long)((ulong)x >>> ((64 - y) & 63));
static void Main()
{
Console.WriteLine(Test(0x12345678, 0));
Console.WriteLine(Test(0x12345678, 32));
Console.WriteLine(TestLong(0x1234, 0));
}
}
Both shift counts are explicitly masked, so no unspecified out-of-range shift behavior is involved.
Expected
(also what DOTNET_JITMinOpts=1 produces)
Actual
The entire XOR tree collapses to a single rol, which is a no-op for count 0:
; Program:Test(int,int):int (FullOpts)
mov eax, ecx
mov ecx, edx
rol eax, cl
ret
Notes
fgRecognizeAndMorphBitwiseRotation (morph.cpp) has no nonzero-count requirement for the GT_XOR root.
The GT_OR root is fine — (x << 0) | (x >>> N) yields x, matching ROL(x, 0).
Only the GT_XOR root with a variable rotate amount is affected; constant counts of 0 are already rejected
earlier by the overmask check.
Possible fix: only allow a GT_XOR root when the rotate amount is provably not a multiple of the operand width.
Also reproduces on .NET 10.0.12.
fgOperIsBitwiseRotationRootacceptsGT_XORas a rotation root, butORandXORare only interchangeable whenthe two shifted values have disjoint bits. With a variable count that is a multiple of the operand width both
sub-shifts equal
x, so theXORmust be0whileROL(x, 0)isx.Minimal Repro
Both shift counts are explicitly masked, so no unspecified out-of-range shift behavior is involved.
Expected
(also what
DOTNET_JITMinOpts=1produces)Actual
The entire
XORtree collapses to a singlerol, which is a no-op for count0:Notes
fgRecognizeAndMorphBitwiseRotation(morph.cpp) has no nonzero-count requirement for theGT_XORroot.The
GT_ORroot is fine —(x << 0) | (x >>> N)yieldsx, matchingROL(x, 0).Only the
GT_XORroot with a variable rotate amount is affected; constant counts of0are already rejectedearlier by the overmask check.
Possible fix: only allow a
GT_XORroot when the rotate amount is provably not a multiple of the operand width.Also reproduces on .NET 10.0.12.