From af25fe652ec08fda63c23ae8f61470a48e65660e Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Wed, 10 Feb 2021 21:03:35 +0300 Subject: [PATCH] [RyuJIT] Implement Interlocked.And and Interlocked.Or for arm64-v8.1 (#46253) --- src/coreclr/jit/codegenarm64.cpp | 24 ++- src/coreclr/jit/codegenarmarch.cpp | 2 + src/coreclr/jit/codegenxarch.cpp | 5 + src/coreclr/jit/decomposelongs.cpp | 2 + src/coreclr/jit/emitarm64.cpp | 2 + src/coreclr/jit/gentree.cpp | 4 +- src/coreclr/jit/gentree.h | 13 +- src/coreclr/jit/gtlist.h | 2 + src/coreclr/jit/importer.cpp | 33 ++++ src/coreclr/jit/instrsarm64.h | 6 + src/coreclr/jit/liveness.cpp | 4 + src/coreclr/jit/lower.cpp | 4 + src/coreclr/jit/lowerarmarch.cpp | 2 + src/coreclr/jit/lsraarm64.cpp | 7 + src/coreclr/jit/lsraxarch.cpp | 2 + src/coreclr/jit/namedintrinsiclist.h | 3 + src/coreclr/jit/optimizer.cpp | 4 +- src/coreclr/jit/valuenum.cpp | 4 +- .../src/System/Threading/Interlocked.cs | 4 + .../interlocked/and_or/and_or_int32.cs | 144 ++++++++++++++++++ .../interlocked/and_or/and_or_int32.csproj | 10 ++ .../interlocked/and_or/and_or_int64.cs | 144 ++++++++++++++++++ .../interlocked/and_or/and_or_int64.csproj | 10 ++ 23 files changed, 429 insertions(+), 6 deletions(-) create mode 100644 src/tests/baseservices/threading/interlocked/and_or/and_or_int32.cs create mode 100644 src/tests/baseservices/threading/interlocked/and_or/and_or_int32.csproj create mode 100644 src/tests/baseservices/threading/interlocked/and_or/and_or_int64.cs create mode 100644 src/tests/baseservices/threading/interlocked/and_or/and_or_int64.csproj diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 0406fb2b70e4c..5efee1618dce5 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -2789,10 +2789,10 @@ void CodeGen::genJumpTable(GenTree* treeNode) } //------------------------------------------------------------------------ -// genLockedInstructions: Generate code for a GT_XADD or GT_XCHG node. +// genLockedInstructions: Generate code for a GT_XADD, GT_XAND, GT_XORR or GT_XCHG node. // // Arguments: -// treeNode - the GT_XADD/XCHG node +// treeNode - the GT_XADD/XAND/XORR/XCHG node // void CodeGen::genLockedInstructions(GenTreeOp* treeNode) { @@ -2813,6 +2813,19 @@ void CodeGen::genLockedInstructions(GenTreeOp* treeNode) switch (treeNode->gtOper) { + case GT_XORR: + GetEmitter()->emitIns_R_R_R(INS_ldsetal, dataSize, dataReg, (targetReg == REG_NA) ? REG_ZR : targetReg, + addrReg); + break; + case GT_XAND: + { + // Grab a temp reg to perform `MVN` for dataReg first. + regNumber tempReg = treeNode->GetSingleTempReg(); + GetEmitter()->emitIns_R_R(INS_mvn, dataSize, tempReg, dataReg); + GetEmitter()->emitIns_R_R_R(INS_ldclral, dataSize, tempReg, (targetReg == REG_NA) ? REG_ZR : targetReg, + addrReg); + break; + } case GT_XCHG: GetEmitter()->emitIns_R_R_R(INS_swpal, dataSize, dataReg, targetReg, addrReg); break; @@ -2826,6 +2839,9 @@ void CodeGen::genLockedInstructions(GenTreeOp* treeNode) } else { + // These are imported normally if Atomics aren't supported. + assert(!treeNode->OperIs(GT_XORR, GT_XAND)); + regNumber exResultReg = treeNode->ExtractTempReg(RBM_ALLINT); regNumber storeDataReg = (treeNode->OperGet() == GT_XCHG) ? dataReg : treeNode->ExtractTempReg(RBM_ALLINT); regNumber loadReg = (targetReg != REG_NA) ? targetReg : storeDataReg; @@ -6217,6 +6233,10 @@ void CodeGen::genArm64EmitterUnitTests() theEmitter->emitIns_R_R_R(INS_ldadd, EA_8BYTE, REG_R8, REG_R9, REG_R10); theEmitter->emitIns_R_R_R(INS_ldadda, EA_8BYTE, REG_R8, REG_R9, REG_R10); theEmitter->emitIns_R_R_R(INS_ldaddal, EA_8BYTE, REG_R8, REG_R9, REG_R10); + theEmitter->emitIns_R_R_R(INS_ldclral, EA_4BYTE, REG_R8, REG_R9, REG_R10); + theEmitter->emitIns_R_R_R(INS_ldclral, EA_8BYTE, REG_R8, REG_R9, REG_R10); + theEmitter->emitIns_R_R_R(INS_ldsetal, EA_4BYTE, REG_R8, REG_R9, REG_R10); + theEmitter->emitIns_R_R_R(INS_ldsetal, EA_8BYTE, REG_R8, REG_R9, REG_R10); theEmitter->emitIns_R_R_R(INS_ldaddl, EA_8BYTE, REG_R8, REG_R9, REG_R10); theEmitter->emitIns_R_R_R(INS_swpb, EA_4BYTE, REG_R8, REG_R9, REG_R10); theEmitter->emitIns_R_R_R(INS_swpab, EA_4BYTE, REG_R8, REG_R9, REG_R10); diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index ed7277ea31a85..499a89faf4deb 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -415,6 +415,8 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) #ifdef TARGET_ARM64 case GT_XCHG: + case GT_XORR: + case GT_XAND: case GT_XADD: genLockedInstructions(treeNode->AsOp()); break; diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index f4467fecac0da..b4f4c08070bd5 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -1735,6 +1735,11 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genLockedInstructions(treeNode->AsOp()); break; + case GT_XORR: + case GT_XAND: + NYI("Interlocked.Or and Interlocked.And aren't implemented for x86 yet."); + break; + case GT_MEMORYBARRIER: { CodeGen::BarrierKind barrierKind = diff --git a/src/coreclr/jit/decomposelongs.cpp b/src/coreclr/jit/decomposelongs.cpp index 33650892924a5..092e6b54fc654 100644 --- a/src/coreclr/jit/decomposelongs.cpp +++ b/src/coreclr/jit/decomposelongs.cpp @@ -247,6 +247,8 @@ GenTree* DecomposeLongs::DecomposeNode(GenTree* tree) #endif // FEATURE_SIMD case GT_LOCKADD: + case GT_XORR: + case GT_XAND: case GT_XADD: case GT_XCHG: case GT_CMPXCHG: diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index eaac34ff42791..7be5d50604992 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -6105,6 +6105,8 @@ void emitter::emitIns_R_R_R( case INS_ldadda: case INS_ldaddal: case INS_ldaddl: + case INS_ldclral: + case INS_ldsetal: case INS_swpb: case INS_swpab: case INS_swpalb: diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 34aa90fc769a5..3d9803373ea13 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -5483,7 +5483,7 @@ GenTree* GenTree::gtGetParent(GenTree*** parentChildPtrPtr) const bool GenTree::OperRequiresAsgFlag() { - if (OperIs(GT_ASG) || OperIs(GT_XADD, GT_XCHG, GT_LOCKADD, GT_CMPXCHG, GT_MEMORYBARRIER)) + if (OperIs(GT_ASG) || OperIs(GT_XADD, GT_XORR, GT_XAND, GT_XCHG, GT_LOCKADD, GT_CMPXCHG, GT_MEMORYBARRIER)) { return true; } @@ -5558,6 +5558,8 @@ bool GenTree::OperIsImplicitIndir() const switch (gtOper) { case GT_LOCKADD: + case GT_XORR: + case GT_XAND: case GT_XADD: case GT_XCHG: case GT_CMPXCHG: diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 9093bbc8954df..8cf32cee6d546 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -1519,7 +1519,18 @@ struct GenTree static bool OperIsAtomicOp(genTreeOps gtOper) { - return (gtOper == GT_XADD || gtOper == GT_XCHG || gtOper == GT_LOCKADD || gtOper == GT_CMPXCHG); + switch (gtOper) + { + case GT_XADD: + case GT_XORR: + case GT_XAND: + case GT_XCHG: + case GT_LOCKADD: + case GT_CMPXCHG: + return true; + default: + return false; + } } bool OperIsAtomicOp() const diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index f73f0c6ce68d7..638bbf3d39c12 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -53,6 +53,8 @@ GTNODE(ARR_LENGTH , GenTreeArrLen ,0,(GTK_UNOP|GTK_EXOP)) // arr GTNODE(INTRINSIC , GenTreeIntrinsic ,0,(GTK_BINOP|GTK_EXOP)) // intrinsics GTNODE(LOCKADD , GenTreeOp ,0,(GTK_BINOP|GTK_NOVALUE)) +GTNODE(XAND , GenTreeOp ,0,GTK_BINOP) +GTNODE(XORR , GenTreeOp ,0,GTK_BINOP) GTNODE(XADD , GenTreeOp ,0,GTK_BINOP) GTNODE(XCHG , GenTreeOp ,0,GTK_BINOP) GTNODE(CMPXCHG , GenTreeCmpXchg ,0,GTK_SPECIAL) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index acf0975f69016..b8e9cbbba0058 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4337,6 +4337,25 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } +#ifdef TARGET_ARM64 + // Intrinsify Interlocked.Or and Interlocked.And only for arm64-v8.1 (and newer) + // TODO-CQ: Implement for XArch (https://github.com/dotnet/runtime/issues/32239). + case NI_System_Threading_Interlocked_Or: + case NI_System_Threading_Interlocked_And: + { + if (opts.OptimizationEnabled() && compOpportunisticallyDependsOn(InstructionSet_Atomics)) + { + assert(sig->numArgs == 2); + GenTree* op2 = impPopStack().val; + GenTree* op1 = impPopStack().val; + genTreeOps op = (ni == NI_System_Threading_Interlocked_Or) ? GT_XORR : GT_XAND; + retNode = gtNewOperNode(op, genActualType(callType), op1, op2); + retNode->gtFlags |= GTF_GLOB_REF | GTF_ASG; + } + break; + } +#endif // TARGET_ARM64 + #ifdef FEATURE_HW_INTRINSICS case NI_System_Math_FusedMultiplyAdd: { @@ -4894,6 +4913,20 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) result = NI_System_Threading_Thread_get_ManagedThreadId; } } +#ifndef TARGET_ARM64 + // TODO-CQ: Implement for XArch (https://github.com/dotnet/runtime/issues/32239). + else if (strcmp(className, "Interlocked") == 0) + { + if (strcmp(methodName, "And") == 0) + { + result = NI_System_Threading_Interlocked_And; + } + else if (strcmp(methodName, "Or") == 0) + { + result = NI_System_Threading_Interlocked_Or; + } + } +#endif } #if defined(TARGET_XARCH) || defined(TARGET_ARM64) else if (strcmp(namespaceName, "System.Buffers.Binary") == 0) diff --git a/src/coreclr/jit/instrsarm64.h b/src/coreclr/jit/instrsarm64.h index 1bb1357366832..425c8c58f3348 100644 --- a/src/coreclr/jit/instrsarm64.h +++ b/src/coreclr/jit/instrsarm64.h @@ -1194,6 +1194,12 @@ INST1(ldadda, "ldadda", LD|ST, IF_LS_3E, 0xB8A00000) INST1(ldaddal, "ldaddal", LD|ST, IF_LS_3E, 0xB8E00000) // ldaddal Rm, Rt, [Xn] LS_3E 1X111000111mmmmm 000000nnnnnttttt B8E0 0000 Rm Rt Rn ARMv8.1 LSE Atomics +INST1(ldclral, "ldclral", LD|ST, IF_LS_3E, 0xB8E01000) + // ldclral Rm, Rt, [Xn] LS_3E 1X111000111mmmmm 000100nnnnnttttt B8E0 1000 Rm Rt Rn ARMv8.1 LSE Atomics + +INST1(ldsetal, "ldsetal", LD|ST, IF_LS_3E, 0xB8E03000) + // ldsetal Rm, Rt, [Xn] LS_3E 1X111000111mmmmm 001100nnnnnttttt B8E0 3000 Rm Rt Rn ARMv8.1 LSE Atomics + INST1(ldaddl, "ldaddl", LD|ST, IF_LS_3E, 0xB8600000) // ldaddl Rm, Rt, [Xn] LS_3E 1X111000011mmmmm 000000nnnnnttttt B860 0000 Rm Rt Rn ARMv8.1 LSE Atomics diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index d719970adeac9..cae25c9d59353 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -287,6 +287,8 @@ void Compiler::fgPerNodeLocalVarLiveness(GenTree* tree) // We'll assume these are use-then-defs of memory. case GT_LOCKADD: + case GT_XORR: + case GT_XAND: case GT_XADD: case GT_XCHG: case GT_CMPXCHG: @@ -2052,6 +2054,8 @@ void Compiler::fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALAR break; case GT_LOCKADD: + case GT_XORR: + case GT_XAND: case GT_XADD: case GT_XCHG: case GT_CMPXCHG: diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 7bd60f3f21a23..4452e96225f1c 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -318,10 +318,14 @@ GenTree* Lowering::LowerNode(GenTree* node) CheckImmedAndMakeContained(node, node->AsCmpXchg()->gtOpComparand); break; + case GT_XORR: + case GT_XAND: case GT_XADD: CheckImmedAndMakeContained(node, node->AsOp()->gtOp2); break; #elif defined(TARGET_XARCH) + case GT_XORR: + case GT_XAND: case GT_XADD: if (node->IsUnusedValue()) { diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 80e2bc2c5db81..430d68094cbb6 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -76,6 +76,8 @@ bool Lowering::IsContainableImmed(GenTree* parentNode, GenTree* childNode) const #ifdef TARGET_ARM64 case GT_CMPXCHG: case GT_LOCKADD: + case GT_XORR: + case GT_XAND: case GT_XADD: return comp->compOpportunisticallyDependsOn(InstructionSet_Atomics) ? false diff --git a/src/coreclr/jit/lsraarm64.cpp b/src/coreclr/jit/lsraarm64.cpp index 9463b1f482087..d9aa71da582cf 100644 --- a/src/coreclr/jit/lsraarm64.cpp +++ b/src/coreclr/jit/lsraarm64.cpp @@ -425,6 +425,8 @@ int LinearScan::BuildNode(GenTree* tree) break; case GT_LOCKADD: + case GT_XORR: + case GT_XAND: case GT_XADD: case GT_XCHG: { @@ -440,6 +442,11 @@ int LinearScan::BuildNode(GenTree* tree) buildInternalIntRegisterDefForNode(tree); } } + else if (tree->OperIs(GT_XAND)) + { + // for ldclral we need an internal register. + buildInternalIntRegisterDefForNode(tree); + } assert(!tree->gtGetOp1()->isContained()); RefPosition* op1Use = BuildUse(tree->gtGetOp1()); diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index bb07b384d9c9e..3872e794aa884 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -449,6 +449,8 @@ int LinearScan::BuildNode(GenTree* tree) } break; + case GT_XORR: + case GT_XAND: case GT_XADD: case GT_XCHG: { diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 9777f498a80a0..79214e2d39595 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -62,6 +62,9 @@ enum NamedIntrinsic : unsigned short NI_IsSupported_Dynamic, NI_Throw_PlatformNotSupportedException, + NI_System_Threading_Interlocked_And, + NI_System_Threading_Interlocked_Or, + #ifdef FEATURE_HW_INTRINSICS NI_HW_INTRINSIC_START, #if defined(TARGET_XARCH) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index ddadd938fcfc6..5655b609f291e 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7155,7 +7155,7 @@ void Compiler::optHoistLoopBlocks(unsigned loopNum, ArrayStack* blo m_beforeSideEffect = false; } } - else if (tree->OperIs(GT_XADD, GT_XCHG, GT_LOCKADD, GT_CMPXCHG, GT_MEMORYBARRIER)) + else if (tree->OperIs(GT_XADD, GT_XORR, GT_XAND, GT_XCHG, GT_LOCKADD, GT_CMPXCHG, GT_MEMORYBARRIER)) { // If this node is a MEMORYBARRIER or an Atomic operation // then don't hoist and stop any further hoisting after this node @@ -7969,6 +7969,8 @@ bool Compiler::optComputeLoopSideEffectsOfBlock(BasicBlock* blk) break; case GT_LOCKADD: + case GT_XORR: + case GT_XAND: case GT_XADD: case GT_XCHG: case GT_CMPXCHG: diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 00a4a9fbef5b3..11825b08952d8 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -5557,7 +5557,7 @@ void ValueNumStore::vnDumpSimdType(Compiler* comp, VNFuncApp* simdType) static UINT8 vnfOpAttribs[VNF_COUNT]; static genTreeOps genTreeOpsIllegalAsVNFunc[] = {GT_IND, // When we do heap memory. GT_NULLCHECK, GT_QMARK, GT_COLON, GT_LOCKADD, GT_XADD, GT_XCHG, - GT_CMPXCHG, GT_LCLHEAP, GT_BOX, + GT_CMPXCHG, GT_LCLHEAP, GT_BOX, GT_XORR, GT_XAND, // These need special semantics: GT_COMMA, // == second argument (but with exception(s) from first). @@ -8538,6 +8538,8 @@ void Compiler::fgValueNumberTree(GenTree* tree) noway_assert("LOCKADD should not appear before lowering"); break; + case GT_XORR: // Binop + case GT_XAND: // Binop case GT_XADD: // Binop case GT_XCHG: // Binop { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs index 7058e5f13364c..e2dceefb632c6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs @@ -164,6 +164,7 @@ public static ulong Read(ref ulong location) => /// The value to be combined with the integer at . /// The original value in . /// The address of is a null pointer. + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int And(ref int location1, int value) { @@ -195,6 +196,7 @@ public static uint And(ref uint location1, uint value) => /// The value to be combined with the integer at . /// The original value in . /// The address of is a null pointer. + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long And(ref long location1, long value) { @@ -228,6 +230,7 @@ public static ulong And(ref ulong location1, ulong value) => /// The value to be combined with the integer at . /// The original value in . /// The address of is a null pointer. + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Or(ref int location1, int value) { @@ -259,6 +262,7 @@ public static uint Or(ref uint location1, uint value) => /// The value to be combined with the integer at . /// The original value in . /// The address of is a null pointer. + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long Or(ref long location1, long value) { diff --git a/src/tests/baseservices/threading/interlocked/and_or/and_or_int32.cs b/src/tests/baseservices/threading/interlocked/and_or/and_or_int32.cs new file mode 100644 index 0000000000000..a5f2207422070 --- /dev/null +++ b/src/tests/baseservices/threading/interlocked/and_or/and_or_int32.cs @@ -0,0 +1,144 @@ +// 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 System.Threading; + +public class Program +{ + private static int s_RetCode = 100; + + public static int Main() + { + int[] testData = new int[] { int.MinValue, int.MinValue + 1, -1, 0, 1, 2, 1000, int.MaxValue - 1, int.MaxValue }; + for (int i = 0; i < testData.Length; i++) + { + for (int j = 0; j < testData.Length; j++) + { + // XAnd + int test1Value = testData[i]; + int test1Arg = testData[j]; + int ret1Value = RefImpl.XAnd32(ref test1Value, test1Arg); + + int test2Value = testData[i]; + int test2Arg = testData[j]; + int ret2Value = InterlockedImpl.XAnd32(ref test2Value, test2Arg); + AssertEquals(test1Value, test2Value); + AssertEquals(ret1Value, ret2Value); + + // XAnd_noret + int test3Value = testData[i]; + int test3Arg = testData[j]; + RefImpl.XAnd32_noret(ref test3Value, test3Arg); + + int test4Value = testData[i]; + int test4Arg = testData[j]; + InterlockedImpl.XAnd32_noret(ref test4Value, test4Arg); + AssertEquals(test3Value, test4Value); + + // XOr + int test5Value = testData[i]; + int test5Arg = testData[j]; + int ret5Value = RefImpl.XOr32(ref test5Value, test5Arg); + + int test6Value = testData[i]; + int test6Arg = testData[j]; + int ret6Value = InterlockedImpl.XOr32(ref test6Value, test6Arg); + AssertEquals(test5Value, test6Value); + AssertEquals(ret5Value, ret6Value); + + // XOr_noret + int test7Value = testData[i]; + int test7Arg = testData[j]; + RefImpl.XOr32_noret(ref test7Value, test7Arg); + + int test8Value = testData[i]; + int test8Arg = testData[j]; + InterlockedImpl.XOr32_noret(ref test8Value, test8Arg); + AssertEquals(test7Value, test8Value); + } + + ThrowsNRE(() => + { + ref int nullref = ref Unsafe.NullRef(); + InterlockedImpl.XAnd32(ref nullref, testData[i]); + }); + + ThrowsNRE(() => + { + ref int nullref = ref Unsafe.NullRef(); + InterlockedImpl.XAnd32_noret(ref nullref, testData[i]); + }); + + ThrowsNRE(() => + { + ref int nullref = ref Unsafe.NullRef(); + InterlockedImpl.XOr32(ref nullref, testData[i]); + }); + + ThrowsNRE(() => + { + ref int nullref = ref Unsafe.NullRef(); + InterlockedImpl.XOr32_noret(ref nullref, testData[i]); + }); + } + + + return s_RetCode; + } + + static void ThrowsNRE(Action action) + { + try + { + action(); + } + catch (NullReferenceException) + { + return; + } + + Console.WriteLine("ERROR: NullReferenceException was expected"); + s_RetCode++; + } + + static void AssertEquals(int expected, int actual, [CallerLineNumber] int line = 0) + { + if (expected != actual) + { + Console.WriteLine($"ERROR: {expected} != {actual} (Line:{line})"); + s_RetCode++; + } + } +} + +class RefImpl +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static int XAnd32(ref int a, int b) { int src = a; a &= b; return src; } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void XAnd32_noret(ref int a, int b) => a &= b; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int XOr32(ref int a, int b) { int src = a; a |= b; return src; } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void XOr32_noret(ref int a, int b) => a |= b; +} + +class InterlockedImpl +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static int XAnd32(ref int a, int b) => Interlocked.And(ref a, b); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void XAnd32_noret(ref int a, int b) => Interlocked.And(ref a, b); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int XOr32(ref int a, int b) => Interlocked.Or(ref a, b); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void XOr32_noret(ref int a, int b) => Interlocked.Or(ref a, b); +} \ No newline at end of file diff --git a/src/tests/baseservices/threading/interlocked/and_or/and_or_int32.csproj b/src/tests/baseservices/threading/interlocked/and_or/and_or_int32.csproj new file mode 100644 index 0000000000000..19781e26c20d8 --- /dev/null +++ b/src/tests/baseservices/threading/interlocked/and_or/and_or_int32.csproj @@ -0,0 +1,10 @@ + + + Exe + + True + + + + + diff --git a/src/tests/baseservices/threading/interlocked/and_or/and_or_int64.cs b/src/tests/baseservices/threading/interlocked/and_or/and_or_int64.cs new file mode 100644 index 0000000000000..01f7e014cff34 --- /dev/null +++ b/src/tests/baseservices/threading/interlocked/and_or/and_or_int64.cs @@ -0,0 +1,144 @@ +// 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 System.Threading; + +public class Program +{ + private static int s_RetCode = 100; + + public static int Main() + { + long[] testData = new long[] { long.MinValue, long.MinValue + 1, -1, 0, 1, 2, 1000, long.MaxValue - 1, long.MaxValue }; + for (long i = 0; i < testData.Length; i++) + { + for (long j = 0; j < testData.Length; j++) + { + // XAnd + long test1Value = testData[i]; + long test1Arg = testData[j]; + long ret1Value = RefImpl.XAnd64(ref test1Value, test1Arg); + + long test2Value = testData[i]; + long test2Arg = testData[j]; + long ret2Value = InterlockedImpl.XAnd64(ref test2Value, test2Arg); + AssertEquals(test1Value, test2Value); + AssertEquals(ret1Value, ret2Value); + + // XAnd_noret + long test3Value = testData[i]; + long test3Arg = testData[j]; + RefImpl.XAnd64_noret(ref test3Value, test3Arg); + + long test4Value = testData[i]; + long test4Arg = testData[j]; + InterlockedImpl.XAnd64_noret(ref test4Value, test4Arg); + AssertEquals(test3Value, test4Value); + + // XOr + long test5Value = testData[i]; + long test5Arg = testData[j]; + long ret5Value = RefImpl.XOr64(ref test5Value, test5Arg); + + long test6Value = testData[i]; + long test6Arg = testData[j]; + long ret6Value = InterlockedImpl.XOr64(ref test6Value, test6Arg); + AssertEquals(test5Value, test6Value); + AssertEquals(ret5Value, ret6Value); + + // XOr_noret + long test7Value = testData[i]; + long test7Arg = testData[j]; + RefImpl.XOr64_noret(ref test7Value, test7Arg); + + long test8Value = testData[i]; + long test8Arg = testData[j]; + InterlockedImpl.XOr64_noret(ref test8Value, test8Arg); + AssertEquals(test7Value, test8Value); + } + + ThrowsNRE(() => + { + ref long nullref = ref Unsafe.NullRef(); + InterlockedImpl.XAnd64(ref nullref, testData[i]); + }); + + ThrowsNRE(() => + { + ref long nullref = ref Unsafe.NullRef(); + InterlockedImpl.XAnd64_noret(ref nullref, testData[i]); + }); + + ThrowsNRE(() => + { + ref long nullref = ref Unsafe.NullRef(); + InterlockedImpl.XOr64(ref nullref, testData[i]); + }); + + ThrowsNRE(() => + { + ref long nullref = ref Unsafe.NullRef(); + InterlockedImpl.XOr64_noret(ref nullref, testData[i]); + }); + } + + + return s_RetCode; + } + + static void ThrowsNRE(Action action) + { + try + { + action(); + } + catch (NullReferenceException) + { + return; + } + + Console.WriteLine("ERROR: NullReferenceException was expected"); + s_RetCode++; + } + + static void AssertEquals(long expected, long actual, [CallerLineNumber] long line = 0) + { + if (expected != actual) + { + Console.WriteLine($"ERROR: {expected} != {actual} (Line:{line})"); + s_RetCode++; + } + } +} + +class RefImpl +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static long XAnd64(ref long a, long b) { long src = a; a &= b; return src; } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void XAnd64_noret(ref long a, long b) => a &= b; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static long XOr64(ref long a, long b) { long src = a; a |= b; return src; } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void XOr64_noret(ref long a, long b) => a |= b; +} + +class InterlockedImpl +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static long XAnd64(ref long a, long b) => Interlocked.And(ref a, b); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void XAnd64_noret(ref long a, long b) => Interlocked.And(ref a, b); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static long XOr64(ref long a, long b) => Interlocked.Or(ref a, b); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void XOr64_noret(ref long a, long b) => Interlocked.Or(ref a, b); +} \ No newline at end of file diff --git a/src/tests/baseservices/threading/interlocked/and_or/and_or_int64.csproj b/src/tests/baseservices/threading/interlocked/and_or/and_or_int64.csproj new file mode 100644 index 0000000000000..19781e26c20d8 --- /dev/null +++ b/src/tests/baseservices/threading/interlocked/and_or/and_or_int64.csproj @@ -0,0 +1,10 @@ + + + Exe + + True + + + + +