Skip to content

Commit

Permalink
arm64: Add support for Bitwise XOR NOT
Browse files Browse the repository at this point in the history
* Contributes towards dotnet#68028

Change-Id: Ifad031a92270668bb3970fb1af817fda05851116
  • Loading branch information
jonathandavies-arm committed Jan 27, 2025
1 parent c47a53d commit ea3bb79
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 9 deletions.
7 changes: 5 additions & 2 deletions src/coreclr/jit/codegenarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2580,7 +2580,7 @@ void CodeGen::genCodeForMulHi(GenTreeOp* treeNode)
genProduceReg(treeNode);
}

// Generate code for ADD, SUB, MUL, DIV, UDIV, AND, AND_NOT, OR and XOR
// Generate code for ADD, SUB, MUL, DIV, UDIV, AND, AND_NOT, OR, XOR and XOR_NOT
// This method is expected to have called genConsumeOperands() before calling it.
void CodeGen::genCodeForBinary(GenTreeOp* tree)
{
Expand All @@ -2589,7 +2589,7 @@ void CodeGen::genCodeForBinary(GenTreeOp* tree)
var_types targetType = tree->TypeGet();
emitter* emit = GetEmitter();

assert(tree->OperIs(GT_ADD, GT_SUB, GT_MUL, GT_DIV, GT_UDIV, GT_AND, GT_AND_NOT, GT_OR, GT_XOR));
assert(tree->OperIs(GT_ADD, GT_SUB, GT_MUL, GT_DIV, GT_UDIV, GT_AND, GT_AND_NOT, GT_OR, GT_XOR, GT_XOR_NOT));

GenTree* op1 = tree->gtGetOp1();
GenTree* op2 = tree->gtGetOp2();
Expand Down Expand Up @@ -4289,6 +4289,9 @@ instruction CodeGen::genGetInsForOper(genTreeOps oper, var_types type)
case GT_XOR:
ins = INS_eor;
break;
case GT_XOR_NOT:
ins = INS_eon;
break;

default:
NYI("Unhandled oper in genGetInsForOper() - integer");
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)

case GT_OR:
case GT_XOR:
case GT_XOR_NOT:
case GT_AND:
case GT_AND_NOT:
assert(varTypeIsIntegralOrI(treeNode));
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27743,7 +27743,8 @@ bool GenTreeHWIntrinsic::OperIsCreateScalarUnsafe() const
//
bool GenTreeHWIntrinsic::OperIsBitwiseHWIntrinsic(genTreeOps oper)
{
return (oper == GT_AND) || (oper == GT_AND_NOT) || (oper == GT_NOT) || (oper == GT_OR) || (oper == GT_XOR);
return (oper == GT_AND) || (oper == GT_AND_NOT) || (oper == GT_NOT) || (oper == GT_OR) || (oper == GT_XOR) ||
(oper == GT_XOR_NOT);
}

//------------------------------------------------------------------------
Expand Down Expand Up @@ -31030,6 +31031,7 @@ bool GenTree::IsVectorPerElementMask(var_types simdBaseType, unsigned simdSize)
case GT_AND_NOT:
case GT_OR:
case GT_XOR:
case GT_XOR_NOT:
{
// We are a binary bitwise operation where both inputs are per-element masks
return intrinsic->Op(1)->IsVectorPerElementMask(simdBaseType, simdSize) &&
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gtlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ GTNODE(MUL_LONG , GenTreeOp ,1,0,GTK_BINOP|DBK_NOTHIR)
// AndNot - emitted on ARM/ARM64 as the BIC instruction. Also used for creating AndNot HWINTRINSIC vector nodes in a cross-ISA manner.
GTNODE(AND_NOT , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)

// XorNot - emitted on ARM64 as the EON instruction.
GTNODE(XOR_NOT , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)

#ifdef TARGET_ARM64
GTNODE(BFIZ , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR) // Bitfield Insert in Zero.
#endif
Expand Down
26 changes: 25 additions & 1 deletion src/coreclr/jit/lowerarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ bool Lowering::IsContainableUnaryOrBinaryOp(GenTree* parentNode, GenTree* childN
}
}

if (childNode->OperIs(GT_LSH, GT_RSH, GT_RSZ) && parentNode->OperIs(GT_AND_NOT))
if (childNode->OperIs(GT_LSH, GT_RSH, GT_RSZ) && parentNode->OperIs(GT_AND_NOT, GT_XOR_NOT))
{
return true;
}
Expand Down Expand Up @@ -652,6 +652,30 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp)
return next;
}
}

if (binOp->OperIs(GT_XOR))
{
GenTree* opNode = nullptr;
GenTree* notNode = nullptr;
if (binOp->gtGetOp1()->OperIs(GT_NOT))
{
notNode = binOp->gtGetOp1();
opNode = binOp->gtGetOp2();
}
else if (binOp->gtGetOp2()->OperIs(GT_NOT))
{
notNode = binOp->gtGetOp2();
opNode = binOp->gtGetOp1();
}

if (notNode != nullptr)
{
binOp->gtOp1 = opNode;
binOp->gtOp2 = notNode->AsUnOp()->gtGetOp1();
binOp->ChangeOper(GT_XOR_NOT);
BlockRange().Remove(notNode);
}
}
#endif
}

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/lsraarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,7 @@ int LinearScan::BuildNode(GenTree* tree)
case GT_AND_NOT:
case GT_OR:
case GT_XOR:
case GT_XOR_NOT:
case GT_LSH:
case GT_RSH:
case GT_RSZ:
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9925,7 +9925,7 @@ GenTree* Compiler::fgOptimizeHWIntrinsic(GenTreeHWIntrinsic* node)
genTreeOps oper = actualOper;

// We shouldn't find AND_NOT nodes since it should only be produced in lowering
assert(oper != GT_AND_NOT);
assert((oper != GT_AND_NOT) && (oper != GT_XOR_NOT));

if (GenTreeHWIntrinsic::OperIsBitwiseHWIntrinsic(oper))
{
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/jit/simd.h
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ void EvaluateUnarySimd(genTreeOps oper, bool scalar, var_types baseType, TSimd*
inline bool IsBinaryBitwiseOperation(genTreeOps oper)
{
return (oper == GT_AND) || (oper == GT_AND_NOT) || (oper == GT_LSH) || (oper == GT_OR) || (oper == GT_ROL) ||
(oper == GT_ROR) || (oper == GT_RSH) || (oper == GT_RSZ) || (oper == GT_XOR);
(oper == GT_ROR) || (oper == GT_RSH) || (oper == GT_RSZ) || (oper == GT_XOR) || (oper == GT_XOR_NOT);
}

template <typename TBase>
Expand Down Expand Up @@ -901,6 +901,11 @@ TBase EvaluateBinaryScalarSpecialized(genTreeOps oper, TBase arg0, TBase arg1)
return arg0 ^ arg1;
}

case GT_XOR_NOT:
{
return arg0 ^ ~arg1;
}

default:
{
unreached();
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8272,7 +8272,7 @@ ValueNum ValueNumStore::EvalHWIntrinsicFunBinary(
if (oper != GT_NONE)
{
// We shouldn't find AND_NOT nodes since it should only be produced in lowering
assert(oper != GT_AND_NOT);
assert((oper != GT_AND_NOT) && (oper != GT_XOR_NOT));

if (varTypeIsMask(type))
{
Expand Down Expand Up @@ -8415,7 +8415,7 @@ ValueNum ValueNumStore::EvalHWIntrinsicFunBinary(
genTreeOps oper = tree->GetOperForHWIntrinsicId(&isScalar);

// We shouldn't find AND_NOT nodes since it should only be produced in lowering
assert(oper != GT_AND_NOT);
assert((oper != GT_AND_NOT) && (oper != GT_XOR_NOT));

if (isScalar)
{
Expand Down Expand Up @@ -8903,7 +8903,7 @@ ValueNum ValueNumStore::EvalHWIntrinsicFunBinary(
genTreeOps oper = tree->GetOperForHWIntrinsicId(&isScalar);

// We shouldn't find AND_NOT nodes since it should only be produced in lowering
assert(oper != GT_AND_NOT);
assert((oper != GT_AND_NOT) && (oper != GT_XOR_NOT));

if (isScalar)
{
Expand Down
97 changes: 97 additions & 0 deletions src/tests/JIT/opt/InstructionCombining/Eon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.CompilerServices;
using Xunit;

namespace TestEon
{
public class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
[Fact]
public static int CheckEon()
{
bool fail = false;

if (Eon(5, 1) != 0xFFFFFFFB)
{
fail = true;
}

if (EonLSL(0x12345678, 0xA) != 0xEDC92987)
{
fail = true;
}

if (EonLSR(0x87654321, 0xFEDCBA) != 0x789D4A3B)
{
fail = true;
}

if (EonASR(0x2468, 0xFEDCBA) != -0x246C)
{
fail = true;
}

if (EonLargeShift(0x87654321, 0x12345678) != 0xB89ABCDE)
{
fail = true;
}

if (EonLargeShift64Bit(0x1357135713571357, 0x123456789ABCDEF0) != 0xECA8ECA8ECE03DF1)
{
fail = true;
}

if (fail)
{
return 101;
}
return 100;
}

[MethodImpl(MethodImplOptions.NoInlining)]
static uint Eon(uint a, uint b)
{
//ARM64-FULL-LINE: eon {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}
return a ^ ~b;
}

[MethodImpl(MethodImplOptions.NoInlining)]
static uint EonLSL(uint a, uint b)
{
//ARM64-FULL-LINE: eon {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #14
return a ^ ~(b<<14);
}

[MethodImpl(MethodImplOptions.NoInlining)]
static uint EonLSR(uint a, uint b)
{
//ARM64-FULL-LINE: eon {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSR #5
return a ^ ~(b>>5);
}

[MethodImpl(MethodImplOptions.NoInlining)]
static int EonASR(int a, int b)
{
//ARM64-FULL-LINE: eon {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, ASR #22
return a ^ ~(b>>22);
}

[MethodImpl(MethodImplOptions.NoInlining)]
static uint EonLargeShift(uint a, uint b)
{
//ARM64-FULL-LINE: eon {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #27
return a ^ ~(b<<123);
}

[MethodImpl(MethodImplOptions.NoInlining)]
static ulong EonLargeShift64Bit(ulong a, ulong b)
{
//ARM64-FULL-LINE: eon {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, LSR #38
return a ^ ~(b>>166);
}
}
}
17 changes: 17 additions & 0 deletions src/tests/JIT/opt/InstructionCombining/Eon.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for CLRTestEnvironmentVariable -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
</PropertyGroup>
<PropertyGroup>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="Eon.cs">
<HasDisasmCheck>true</HasDisasmCheck>
</Compile>
<CLRTestEnvironmentVariable Include="DOTNET_TieredCompilation" Value="0" />
<CLRTestEnvironmentVariable Include="DOTNET_JITMinOpts" Value="0" />
</ItemGroup>
</Project>

0 comments on commit ea3bb79

Please sign in to comment.