Skip to content

Commit

Permalink
arm64: Add support for Bitwise OR NOT & XOR NOT (#111893)
Browse files Browse the repository at this point in the history
* arm64: Add support for Bitwise XOR NOT

* contributes towards #68028

* arm64: Add support for Bitwise OR NOT

* Update comments & add a tests that takes ints

* Add swap tests that take a int & uint
  • Loading branch information
jonathandavies-arm authored Feb 3, 2025
1 parent 6f7c6fb commit 3a99c64
Show file tree
Hide file tree
Showing 13 changed files with 350 additions and 12 deletions.
11 changes: 9 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, OR_NOT, 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,8 @@ 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_OR_NOT, GT_XOR,
GT_XOR_NOT));

GenTree* op1 = tree->gtGetOp1();
GenTree* op2 = tree->gtGetOp2();
Expand Down Expand Up @@ -4276,6 +4277,9 @@ instruction CodeGen::genGetInsForOper(genTreeOps oper, var_types type)
case GT_OR:
ins = INS_orr;
break;
case GT_OR_NOT:
ins = INS_orn;
break;
case GT_ROR:
ins = INS_ror;
break;
Expand All @@ -4291,6 +4295,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
2 changes: 2 additions & 0 deletions src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
break;

case GT_OR:
case GT_OR_NOT:
case GT_XOR:
case GT_XOR_NOT:
case GT_AND:
case GT_AND_NOT:
assert(varTypeIsIntegralOrI(treeNode));
Expand Down
5 changes: 4 additions & 1 deletion src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27760,7 +27760,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_OR_NOT) ||
(oper == GT_XOR) || (oper == GT_XOR_NOT);
}

//------------------------------------------------------------------------
Expand Down Expand Up @@ -31046,7 +31047,9 @@ bool GenTree::IsVectorPerElementMask(var_types simdBaseType, unsigned simdSize)
case GT_AND:
case GT_AND_NOT:
case GT_OR:
case GT_OR_NOT:
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
6 changes: 6 additions & 0 deletions src/coreclr/jit/gtlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ 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)

// OrNot - emitted on ARM64 as the ORN instruction.
GTNODE(OR_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
33 changes: 32 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_NOT, GT_AND_NOT))
if (childNode->OperIs(GT_LSH, GT_RSH, GT_RSZ) && parentNode->OperIs(GT_NOT, GT_AND_NOT, GT_OR_NOT, GT_XOR_NOT))
{
return true;
}
Expand Down Expand Up @@ -652,6 +652,37 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp)
return next;
}
}

if (binOp->OperIs(GT_OR, 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();
if (binOp->OperIs(GT_OR))
{
binOp->ChangeOper(GT_OR_NOT);
}
else
{
binOp->ChangeOper(GT_XOR_NOT);
}
BlockRange().Remove(notNode);
}
}
#endif
}

Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/lsraarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,9 @@ int LinearScan::BuildNode(GenTree* tree)
case GT_AND:
case GT_AND_NOT:
case GT_OR:
case GT_OR_NOT:
case GT_XOR:
case GT_XOR_NOT:
case GT_LSH:
case GT_RSH:
case GT_RSZ:
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9915,8 +9915,8 @@ GenTree* Compiler::fgOptimizeHWIntrinsic(GenTreeHWIntrinsic* node)
genTreeOps actualOper = node->GetOperForHWIntrinsicId(&isScalar);
genTreeOps oper = actualOper;

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

if (GenTreeHWIntrinsic::OperIsBitwiseHWIntrinsic(oper))
{
Expand Down
15 changes: 13 additions & 2 deletions src/coreclr/jit/simd.h
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,9 @@ 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);
return (oper == GT_AND) || (oper == GT_AND_NOT) || (oper == GT_LSH) || (oper == GT_OR) || (oper == GT_OR_NOT) ||
(oper == GT_ROL) || (oper == GT_ROR) || (oper == GT_RSH) || (oper == GT_RSZ) || (oper == GT_XOR) ||
(oper == GT_XOR_NOT);
}

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

case GT_OR_NOT:
{
return arg0 | ~arg1;
}

case GT_ROL:
{
// Normalize the "rotate by" value
Expand Down Expand Up @@ -901,6 +907,11 @@ TBase EvaluateBinaryScalarSpecialized(genTreeOps oper, TBase arg0, TBase arg1)
return arg0 ^ arg1;
}

case GT_XOR_NOT:
{
return arg0 ^ ~arg1;
}

default:
{
unreached();
Expand Down
8 changes: 4 additions & 4 deletions src/coreclr/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8271,8 +8271,8 @@ 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);
// We shouldn't find AND_NOT, OR_NOT or XOR_NOT nodes since it should only be produced in lowering
assert((oper != GT_AND_NOT) && (oper != GT_OR_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_OR_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_OR_NOT) && (oper != GT_XOR_NOT));

if (isScalar)
{
Expand Down
121 changes: 121 additions & 0 deletions src/tests/JIT/opt/InstructionCombining/Eon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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 (EonLSLSwapInt(0x12345678, 0xA) != -0x1236d679)
{
fail = true;
}

if (EonLSLSwapUint(0xFDFDFDFD, 0xB) != 0x200C202)
{
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 int EonLSLSwapInt(int a, int b)
{
//ARM64-FULL-LINE: eon {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #14
return ~(b<<14) ^ a;
}

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

[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>
Loading

0 comments on commit 3a99c64

Please sign in to comment.