diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index beca2374c6713a..393a28863d6be2 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -4165,16 +4165,23 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, case NI_System_Threading_Interlocked_Or: case NI_System_Threading_Interlocked_And: { +#if defined(TARGET_X86) + // On x86, TYP_LONG is not supported as an intrinsic + if (genActualType(callType) == TYP_LONG) + { + break; + } +#endif + // TODO: Implement support for XAND/XORR with small integer types (byte/short) + if ((callType != TYP_INT) && (callType != TYP_LONG)) + { + break; + } + #if defined(TARGET_ARM64) if (compOpportunisticallyDependsOn(InstructionSet_Atomics)) #endif { -#if defined(TARGET_X86) - if (genActualType(callType) == TYP_LONG) - { - break; - } -#endif assert(sig->numArgs == 2); GenTree* op2 = impPopStack().val; GenTree* op1 = impPopStack().val; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/CacheControlHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/CacheControlHeaderValue.cs index c72e1965e14386..8911d24ce01ee1 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/CacheControlHeaderValue.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/CacheControlHeaderValue.cs @@ -70,11 +70,11 @@ private void SetFlag(Flags flag, bool value) // that concurrent modifications of different properties don't interfere with each other. if (value) { - Interlocked.Or(ref Unsafe.As(ref _flags), (int)flag); + Interlocked.Or(ref _flags, flag); } else { - Interlocked.And(ref Unsafe.As(ref _flags), (int)~flag); + Interlocked.And(ref _flags, ~flag); } } diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 7404f6124e5b16..bf34d7bec20f87 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4388,6 +4388,9 @@ The specified type must be a reference type, a primitive type, or an enum type. + + The specified type must be an integer primitive type or an enum type. + The field is invalid for initializing array or span. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs index f1ce1193e78973..ec40677dc62384 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -444,14 +444,12 @@ private IntPtr AsUserDefined(in Guid riid) private void SetFlag(CreateComInterfaceFlagsEx flag) { - int setMask = (int)flag; - Interlocked.Or(ref Unsafe.As(ref Flags), setMask); + Interlocked.Or(ref Flags, flag); } private void ResetFlag(CreateComInterfaceFlagsEx flag) { - int resetMask = ~(int)flag; - Interlocked.And(ref Unsafe.As(ref Flags), resetMask); + Interlocked.And(ref Flags, ~flag); } private static uint GetTrackerCount(ulong c) 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 a5c1ff9a756c9b..cc33770071fe77 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs @@ -634,6 +634,83 @@ public static long And(ref long location1, long value) [CLSCompliant(false)] public static ulong And(ref ulong location1, ulong value) => (ulong)And(ref Unsafe.As(ref location1), (long)value); + + /// Bitwise "ands" two values of type and replaces the first value with the result, as an atomic operation. + /// A variable containing the first value to be combined. The result is stored in . + /// The value to be combined with the value at . + /// The original value in . + /// The address of is a null pointer. + /// An unsupported is specified. + /// + /// The type to be used for and . + /// This type must be an integer primitive type or an enum type backed by an integer type. + /// Floating-point types (float, double) are not supported. + /// + [Intrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe T And(ref T location1, T value) where T : struct + { + // Only integer primitive types and enum types backed by integer types are supported. + // Floating-point types and floating-point backed enums are not supported. + if ((!typeof(T).IsPrimitive && !typeof(T).IsEnum) || + typeof(T) == typeof(float) || typeof(T) == typeof(double) || + (typeof(T).IsEnum && (typeof(T).GetEnumUnderlyingType() == typeof(float) || typeof(T).GetEnumUnderlyingType() == typeof(double)))) + { + throw new NotSupportedException(SR.NotSupported_IntegerEnumOrPrimitiveTypeRequired); + } + + // For 1-byte and 2-byte types, we need to use CompareExchange-based implementations + // because there are no direct atomic And operations for these sizes. + if (sizeof(T) == 1) + { + byte current = Unsafe.BitCast(location1); + while (true) + { + byte newValue = (byte)(current & Unsafe.BitCast(value)); + byte oldValue = CompareExchange( + ref Unsafe.As(ref location1), + newValue, + current); + if (oldValue == current) + { + return Unsafe.BitCast(oldValue); + } + current = oldValue; + } + } + + if (sizeof(T) == 2) + { + ushort current = Unsafe.BitCast(location1); + while (true) + { + ushort newValue = (ushort)(current & Unsafe.BitCast(value)); + ushort oldValue = CompareExchange( + ref Unsafe.As(ref location1), + newValue, + current); + if (oldValue == current) + { + return Unsafe.BitCast(oldValue); + } + current = oldValue; + } + } + + if (sizeof(T) == 4) + { + return Unsafe.BitCast( + And( + ref Unsafe.As(ref location1), + Unsafe.BitCast(value))); + } + + Debug.Assert(sizeof(T) == 8); + return Unsafe.BitCast( + And( + ref Unsafe.As(ref location1), + Unsafe.BitCast(value))); + } #endregion #region Or @@ -700,6 +777,83 @@ public static long Or(ref long location1, long value) [CLSCompliant(false)] public static ulong Or(ref ulong location1, ulong value) => (ulong)Or(ref Unsafe.As(ref location1), (long)value); + + /// Bitwise "ors" two values of type and replaces the first value with the result, as an atomic operation. + /// A variable containing the first value to be combined. The result is stored in . + /// The value to be combined with the value at . + /// The original value in . + /// The address of is a null pointer. + /// An unsupported is specified. + /// + /// The type to be used for and . + /// This type must be an integer primitive type or an enum type backed by an integer type. + /// Floating-point types (float, double) are not supported. + /// + [Intrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe T Or(ref T location1, T value) where T : struct + { + // Only integer primitive types and enum types backed by integer types are supported. + // Floating-point types and floating-point backed enums are not supported. + if ((!typeof(T).IsPrimitive && !typeof(T).IsEnum) || + typeof(T) == typeof(float) || typeof(T) == typeof(double) || + (typeof(T).IsEnum && (typeof(T).GetEnumUnderlyingType() == typeof(float) || typeof(T).GetEnumUnderlyingType() == typeof(double)))) + { + throw new NotSupportedException(SR.NotSupported_IntegerEnumOrPrimitiveTypeRequired); + } + + // For 1-byte and 2-byte types, we need to use CompareExchange-based implementations + // because there are no direct atomic Or operations for these sizes. + if (sizeof(T) == 1) + { + byte current = Unsafe.BitCast(location1); + while (true) + { + byte newValue = (byte)(current | Unsafe.BitCast(value)); + byte oldValue = CompareExchange( + ref Unsafe.As(ref location1), + newValue, + current); + if (oldValue == current) + { + return Unsafe.BitCast(oldValue); + } + current = oldValue; + } + } + + if (sizeof(T) == 2) + { + ushort current = Unsafe.BitCast(location1); + while (true) + { + ushort newValue = (ushort)(current | Unsafe.BitCast(value)); + ushort oldValue = CompareExchange( + ref Unsafe.As(ref location1), + newValue, + current); + if (oldValue == current) + { + return Unsafe.BitCast(oldValue); + } + current = oldValue; + } + } + + if (sizeof(T) == 4) + { + return Unsafe.BitCast( + Or( + ref Unsafe.As(ref location1), + Unsafe.BitCast(value))); + } + + Debug.Assert(sizeof(T) == 8); + return Unsafe.BitCast( + Or( + ref Unsafe.As(ref location1), + Unsafe.BitCast(value))); + } #endregion #region MemoryBarrier diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 60c279e020b2a1..6e3777358beb99 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -252,7 +252,7 @@ private void InterlockedSetFlags(Flags flags) { // For built-in (simple) parsers, it is safe to do an Interlocked update here Debug.Assert(sizeof(Flags) == sizeof(ulong)); - Interlocked.Or(ref Unsafe.As(ref _flags), (ulong)flags); + Interlocked.Or(ref _flags, flags); } else { diff --git a/src/libraries/System.Private.Uri/src/System/UriScheme.cs b/src/libraries/System.Private.Uri/src/System/UriScheme.cs index c8bf0ba3149197..c1e23fdec6f207 100644 --- a/src/libraries/System.Private.Uri/src/System/UriScheme.cs +++ b/src/libraries/System.Private.Uri/src/System/UriScheme.cs @@ -77,8 +77,8 @@ protected virtual void InitializeAndValidate(Uri uri, out UriFormatException? pa Debug.Assert(sizeof(Uri.Flags) == sizeof(ulong)); // If ParseMinimal is called multiple times this Uri instance may be corrupted, throw an exception instead - ulong previous = Interlocked.Or(ref Unsafe.As(ref uri._flags), (ulong)Uri.Flags.CustomParser_ParseMinimalAlreadyCalled); - if (((Uri.Flags)previous & Uri.Flags.CustomParser_ParseMinimalAlreadyCalled) != 0) + Uri.Flags previous = Interlocked.Or(ref uri._flags, Uri.Flags.CustomParser_ParseMinimalAlreadyCalled); + if ((previous & Uri.Flags.CustomParser_ParseMinimalAlreadyCalled) != 0) { throw new InvalidOperationException(SR.net_uri_InitializeCalledAlreadyOrTooLate); } diff --git a/src/libraries/System.Private.Uri/src/System/UriSyntax.cs b/src/libraries/System.Private.Uri/src/System/UriSyntax.cs index 86260fb01c5141..8ee625ab59c313 100644 --- a/src/libraries/System.Private.Uri/src/System/UriSyntax.cs +++ b/src/libraries/System.Private.Uri/src/System/UriSyntax.cs @@ -269,7 +269,7 @@ internal void InternalValidate(Uri thisUri, out UriFormatException? parsingError // InitializeAndValidate should not be called outside of the constructor Debug.Assert(sizeof(Uri.Flags) == sizeof(ulong)); - Interlocked.Or(ref Unsafe.As(ref thisUri._flags), (ulong)Uri.Flags.CustomParser_ParseMinimalAlreadyCalled); + Interlocked.Or(ref thisUri._flags, Uri.Flags.CustomParser_ParseMinimalAlreadyCalled); } internal string? InternalResolve(Uri thisBaseUri, Uri uriLink, out UriFormatException? parsingError) diff --git a/src/libraries/System.Threading/ref/System.Threading.cs b/src/libraries/System.Threading/ref/System.Threading.cs index 18436238c58a52..c58260ba13aa4d 100644 --- a/src/libraries/System.Threading/ref/System.Threading.cs +++ b/src/libraries/System.Threading/ref/System.Threading.cs @@ -255,6 +255,7 @@ public static partial class Interlocked public static uint And(ref uint location1, uint value) { throw null; } [System.CLSCompliantAttribute(false)] public static ulong And(ref ulong location1, ulong value) { throw null; } + public static T And(ref T location1, T value) where T : struct { throw null; } public static double CompareExchange(ref double location1, double value, double comparand) { throw null; } public static byte CompareExchange(ref byte location1, byte value, byte comparand) { throw null; } [System.CLSCompliantAttribute(false)] @@ -317,6 +318,7 @@ public static void MemoryBarrierProcessWide() { } public static uint Or(ref uint location1, uint value) { throw null; } [System.CLSCompliantAttribute(false)] public static ulong Or(ref ulong location1, ulong value) { throw null; } + public static T Or(ref T location1, T value) where T : struct { throw null; } public static long Read(ref readonly long location) { throw null; } [System.CLSCompliantAttribute(false)] public static ulong Read(ref readonly ulong location) { throw null; } diff --git a/src/libraries/System.Threading/tests/InterlockedTests.cs b/src/libraries/System.Threading/tests/InterlockedTests.cs index b1e4ac92f8f7a3..2d161983d5c684 100644 --- a/src/libraries/System.Threading/tests/InterlockedTests.cs +++ b/src/libraries/System.Threading/tests/InterlockedTests.cs @@ -912,6 +912,402 @@ public void InterlockedOr_UInt64() Assert.Equal(0x17755771u, value); } + [Theory] + [InlineData((byte)0xF0, (byte)0x3C, (byte)0x30)] + [InlineData((byte)0xFF, (byte)0x00, (byte)0x00)] + [InlineData((byte)0xAA, (byte)0x55, (byte)0x00)] + [InlineData((byte)0xFF, (byte)0xFF, (byte)0xFF)] + [InlineData((byte)0x00, (byte)0xFF, (byte)0x00)] + public void InterlockedAnd_Generic_Byte(byte initial, byte operand, byte expected) + { + byte value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_Byte_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), (byte)0x3C)); + } + + [Theory] + [InlineData((sbyte)0x70, (sbyte)0x21, (sbyte)0x20)] + [InlineData((sbyte)-1, (sbyte)0x7F, (sbyte)0x7F)] + [InlineData((sbyte)-128, (sbyte)127, (sbyte)0)] + [InlineData((sbyte)-1, (sbyte)-1, (sbyte)-1)] + [InlineData((sbyte)0, (sbyte)-1, (sbyte)0)] + public void InterlockedAnd_Generic_SByte(sbyte initial, sbyte operand, sbyte expected) + { + sbyte value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_SByte_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), (sbyte)0x21)); + } + + [Theory] + [InlineData((ushort)0x1234, (ushort)0x5678, (ushort)0x1230)] + [InlineData((ushort)0xFFFF, (ushort)0x0000, (ushort)0x0000)] + [InlineData((ushort)0xAAAA, (ushort)0x5555, (ushort)0x0000)] + [InlineData((ushort)0xFFFF, (ushort)0xFFFF, (ushort)0xFFFF)] + [InlineData((ushort)0x0000, (ushort)0xFFFF, (ushort)0x0000)] + public void InterlockedAnd_Generic_UInt16(ushort initial, ushort operand, ushort expected) + { + ushort value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_UInt16_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), (ushort)0x5678)); + } + + [Theory] + [InlineData((short)0x1234, (short)0x5678, (short)0x1230)] + [InlineData((short)-1, (short)0x7FFF, (short)0x7FFF)] + [InlineData((short)-32768, (short)32767, (short)0)] + [InlineData((short)-1, (short)-1, (short)-1)] + [InlineData((short)0, (short)-1, (short)0)] + public void InterlockedAnd_Generic_Int16(short initial, short operand, short expected) + { + short value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_Int16_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), (short)0x5678)); + } + + [Theory] + [InlineData(0x12345670, 0x07654321, 0x02244220)] + [InlineData(-1, 0x7FFFFFFF, 0x7FFFFFFF)] + [InlineData(int.MinValue, int.MaxValue, 0)] + [InlineData(-1, -1, -1)] + [InlineData(0, -1, 0)] + public void InterlockedAnd_Generic_Int32(int initial, int operand, int expected) + { + int value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_Int32_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), 0x7654321)); + } + + [Theory] + [InlineData(0x12345670u, 0x07654321u, 0x02244220u)] + [InlineData(0xFFFFFFFFu, 0x00000000u, 0x00000000u)] + [InlineData(0xAAAAAAAAu, 0x55555555u, 0x00000000u)] + [InlineData(0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu)] + [InlineData(0x00000000u, 0xFFFFFFFFu, 0x00000000u)] + public void InterlockedAnd_Generic_UInt32(uint initial, uint operand, uint expected) + { + uint value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_UInt32_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), 0x7654321u)); + } + + [Theory] + [InlineData(0x12345670L, 0x07654321L, 0x02244220L)] + [InlineData(-1L, 0x7FFFFFFFFFFFFFFFL, 0x7FFFFFFFFFFFFFFFL)] + [InlineData(long.MinValue, long.MaxValue, 0L)] + [InlineData(-1L, -1L, -1L)] + [InlineData(0L, -1L, 0L)] + public void InterlockedAnd_Generic_Int64(long initial, long operand, long expected) + { + long value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_Int64_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), 0x7654321L)); + } + + [Theory] + [InlineData(0x12345670UL, 0x07654321UL, 0x02244220UL)] + [InlineData(0xFFFFFFFFFFFFFFFFUL, 0x0000000000000000UL, 0x0000000000000000UL)] + [InlineData(0xAAAAAAAAAAAAAAAAUL, 0x5555555555555555UL, 0x0000000000000000UL)] + [InlineData(0xFFFFFFFFFFFFFFFFUL, 0xFFFFFFFFFFFFFFFFUL, 0xFFFFFFFFFFFFFFFFUL)] + [InlineData(0x0000000000000000UL, 0xFFFFFFFFFFFFFFFFUL, 0x0000000000000000UL)] + public void InterlockedAnd_Generic_UInt64(ulong initial, ulong operand, ulong expected) + { + ulong value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_UInt64_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), 0x7654321UL)); + } + + [Theory] + [InlineData(DayOfWeek.Sunday | DayOfWeek.Monday | DayOfWeek.Tuesday, DayOfWeek.Sunday | DayOfWeek.Monday, DayOfWeek.Sunday | DayOfWeek.Monday)] + [InlineData((DayOfWeek)0x7, (DayOfWeek)0x3, (DayOfWeek)0x3)] + [InlineData((DayOfWeek)0xFF, (DayOfWeek)0x00, (DayOfWeek)0x00)] + [InlineData((DayOfWeek)0xFF, (DayOfWeek)0xFF, (DayOfWeek)0xFF)] + public void InterlockedAnd_Generic_Enum(DayOfWeek initial, DayOfWeek operand, DayOfWeek expected) + { + DayOfWeek value = initial; + Assert.Equal(initial, Interlocked.And(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedAnd_Generic_Enum_NullRef() + { + Assert.Throws(() => Interlocked.And(ref Unsafe.NullRef(), DayOfWeek.Monday)); + } + + [Fact] + public void InterlockedAnd_Generic_Float_ThrowsNotSupported() + { + float value = 1.0f; + Assert.Throws(() => Interlocked.And(ref value, 1.0f)); + } + + [Fact] + public void InterlockedAnd_Generic_Double_ThrowsNotSupported() + { + double value = 1.0; + Assert.Throws(() => Interlocked.And(ref value, 1.0)); + } + + [Fact] + public void InterlockedAnd_Generic_Half_ThrowsNotSupported() + { + Half value = (Half)1.0; + Assert.Throws(() => Interlocked.And(ref value, (Half)1.0)); + } + + [Fact] + public void InterlockedAnd_Generic_Unsupported() + { + DateTime value = default; + Assert.Throws(() => Interlocked.And(ref value, default)); + } + + [Theory] + [InlineData((byte)0xF0, (byte)0x0C, (byte)0xFC)] + [InlineData((byte)0x00, (byte)0xFF, (byte)0xFF)] + [InlineData((byte)0xAA, (byte)0x55, (byte)0xFF)] + [InlineData((byte)0xFF, (byte)0x00, (byte)0xFF)] + [InlineData((byte)0x00, (byte)0x00, (byte)0x00)] + public void InterlockedOr_Generic_Byte(byte initial, byte operand, byte expected) + { + byte value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_Byte_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), (byte)0x0C)); + } + + [Theory] + [InlineData((sbyte)0x50, (sbyte)0x21, (sbyte)0x71)] + [InlineData((sbyte)-128, (sbyte)127, (sbyte)-1)] + [InlineData((sbyte)0, (sbyte)-1, (sbyte)-1)] + [InlineData((sbyte)-1, (sbyte)0, (sbyte)-1)] + [InlineData((sbyte)0, (sbyte)0, (sbyte)0)] + public void InterlockedOr_Generic_SByte(sbyte initial, sbyte operand, sbyte expected) + { + sbyte value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_SByte_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), (sbyte)0x21)); + } + + [Theory] + [InlineData((ushort)0x1234, (ushort)0x5008, (ushort)0x523C)] + [InlineData((ushort)0x0000, (ushort)0xFFFF, (ushort)0xFFFF)] + [InlineData((ushort)0xAAAA, (ushort)0x5555, (ushort)0xFFFF)] + [InlineData((ushort)0xFFFF, (ushort)0x0000, (ushort)0xFFFF)] + [InlineData((ushort)0x0000, (ushort)0x0000, (ushort)0x0000)] + public void InterlockedOr_Generic_UInt16(ushort initial, ushort operand, ushort expected) + { + ushort value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_UInt16_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), (ushort)0x5008)); + } + + [Theory] + [InlineData((short)0x1234, (short)0x5008, (short)0x523C)] + [InlineData((short)-32768, (short)32767, (short)-1)] + [InlineData((short)0, (short)-1, (short)-1)] + [InlineData((short)-1, (short)0, (short)-1)] + [InlineData((short)0, (short)0, (short)0)] + public void InterlockedOr_Generic_Int16(short initial, short operand, short expected) + { + short value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_Int16_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), (short)0x5008)); + } + + [Theory] + [InlineData(0x12345670, 0x07654321, 0x17755771)] + [InlineData(int.MinValue, int.MaxValue, -1)] + [InlineData(0, -1, -1)] + [InlineData(-1, 0, -1)] + [InlineData(0, 0, 0)] + public void InterlockedOr_Generic_Int32(int initial, int operand, int expected) + { + int value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_Int32_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), 0x7654321)); + } + + [Theory] + [InlineData(0x12345670u, 0x07654321u, 0x17755771u)] + [InlineData(0x00000000u, 0xFFFFFFFFu, 0xFFFFFFFFu)] + [InlineData(0xAAAAAAAAu, 0x55555555u, 0xFFFFFFFFu)] + [InlineData(0xFFFFFFFFu, 0x00000000u, 0xFFFFFFFFu)] + [InlineData(0x00000000u, 0x00000000u, 0x00000000u)] + public void InterlockedOr_Generic_UInt32(uint initial, uint operand, uint expected) + { + uint value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_UInt32_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), 0x7654321u)); + } + + [Theory] + [InlineData(0x12345670L, 0x07654321L, 0x17755771L)] + [InlineData(long.MinValue, long.MaxValue, -1L)] + [InlineData(0L, -1L, -1L)] + [InlineData(-1L, 0L, -1L)] + [InlineData(0L, 0L, 0L)] + public void InterlockedOr_Generic_Int64(long initial, long operand, long expected) + { + long value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_Int64_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), 0x7654321L)); + } + + [Theory] + [InlineData(0x12345670UL, 0x07654321UL, 0x17755771UL)] + [InlineData(0x0000000000000000UL, 0xFFFFFFFFFFFFFFFFUL, 0xFFFFFFFFFFFFFFFFUL)] + [InlineData(0xAAAAAAAAAAAAAAAAUL, 0x5555555555555555UL, 0xFFFFFFFFFFFFFFFFUL)] + [InlineData(0xFFFFFFFFFFFFFFFFUL, 0x0000000000000000UL, 0xFFFFFFFFFFFFFFFFUL)] + [InlineData(0x0000000000000000UL, 0x0000000000000000UL, 0x0000000000000000UL)] + public void InterlockedOr_Generic_UInt64(ulong initial, ulong operand, ulong expected) + { + ulong value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_UInt64_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), 0x7654321UL)); + } + + [Theory] + [InlineData(DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Sunday | DayOfWeek.Monday)] + [InlineData((DayOfWeek)0x1, (DayOfWeek)0x2, (DayOfWeek)0x3)] + [InlineData((DayOfWeek)0x00, (DayOfWeek)0xFF, (DayOfWeek)0xFF)] + [InlineData((DayOfWeek)0xFF, (DayOfWeek)0x00, (DayOfWeek)0xFF)] + public void InterlockedOr_Generic_Enum(DayOfWeek initial, DayOfWeek operand, DayOfWeek expected) + { + DayOfWeek value = initial; + Assert.Equal(initial, Interlocked.Or(ref value, operand)); + Assert.Equal(expected, value); + } + + [Fact] + public void InterlockedOr_Generic_Enum_NullRef() + { + Assert.Throws(() => Interlocked.Or(ref Unsafe.NullRef(), DayOfWeek.Monday)); + } + + [Fact] + public void InterlockedOr_Generic_Float_ThrowsNotSupported() + { + float value = 1.0f; + Assert.Throws(() => Interlocked.Or(ref value, 1.0f)); + } + + [Fact] + public void InterlockedOr_Generic_Double_ThrowsNotSupported() + { + double value = 1.0; + Assert.Throws(() => Interlocked.Or(ref value, 1.0)); + } + + [Fact] + public void InterlockedOr_Generic_Half_ThrowsNotSupported() + { + Half value = (Half)1.0; + Assert.Throws(() => Interlocked.Or(ref value, (Half)1.0)); + } + + [Fact] + public void InterlockedOr_Generic_Unsupported() + { + DateTime value = default; + Assert.Throws(() => Interlocked.Or(ref value, default)); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void InterlockedIncrement_Multithreaded_Int32() {