Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c71b809
Initial plan
Copilot Oct 22, 2025
0042fe4
Add generic And<T> and Or<T> methods to Interlocked class
Copilot Oct 22, 2025
20e7a17
Add generic And<T> and Or<T> to reference assembly
Copilot Oct 22, 2025
af7262d
Fix test cases to use explicit type arguments for generic And and Or …
Copilot Oct 22, 2025
2334be3
Replace Interlocked.And/Or with Unsafe.As calls with new generic API
Copilot Oct 22, 2025
256f950
Restrict JIT intrinsic expansion for And/Or to TYP_INT and TYP_LONG only
Copilot Oct 22, 2025
0158445
Update src/coreclr/jit/importercalls.cpp
EgorBo Oct 23, 2025
d549cdd
Address PR review feedback: JIT changes and enhanced tests
Copilot Oct 23, 2025
ccdc626
Merge branch 'main' into copilot/add-generic-interlocked-methods
EgorBo Oct 23, 2025
e1935fa
Add TODO comment for small integer support in XAND/XORR
Copilot Oct 23, 2025
6557ab2
Update src/coreclr/jit/importercalls.cpp
EgorBo Oct 23, 2025
79fde5e
Add float/double/Half rejection logic (still being optimized away by …
Copilot Oct 23, 2025
b51b6df
Apply suggestions from code review
EgorBo Oct 23, 2025
82e557d
Reject floating-point backed enums and remove redundant error message
Copilot Oct 23, 2025
315ee04
Simplify validation checks using Type.GetTypeCode()
Copilot Oct 23, 2025
802caa0
Update XML documentation to clarify floating-point types are not supp…
Copilot Oct 24, 2025
834b897
Apply suggestions from code review
EgorBo Oct 24, 2025
a1edac8
Simplify validation check to single-level if statement
Copilot Oct 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Flags, int>(ref _flags), (int)flag);
Interlocked.Or(ref _flags, flag);
}
else
{
Interlocked.And(ref Unsafe.As<Flags, int>(ref _flags), (int)~flag);
Interlocked.And(ref _flags, ~flag);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4388,6 +4388,9 @@
<data name="NotSupported_ReferenceEnumOrPrimitiveTypeRequired" xml:space="preserve">
<value>The specified type must be a reference type, a primitive type, or an enum type.</value>
</data>
<data name="NotSupported_IntegerEnumOrPrimitiveTypeRequired" xml:space="preserve">
<value>The specified type must be an integer primitive type or an enum type.</value>
</data>
<data name="Argument_BadFieldForInitializeArray" xml:space="preserve">
<value>The field is invalid for initializing array or span.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,14 +444,12 @@ private IntPtr AsUserDefined(in Guid riid)

private void SetFlag(CreateComInterfaceFlagsEx flag)
{
int setMask = (int)flag;
Interlocked.Or(ref Unsafe.As<CreateComInterfaceFlagsEx, int>(ref Flags), setMask);
Interlocked.Or(ref Flags, flag);
}

private void ResetFlag(CreateComInterfaceFlagsEx flag)
{
int resetMask = ~(int)flag;
Interlocked.And(ref Unsafe.As<CreateComInterfaceFlagsEx, int>(ref Flags), resetMask);
Interlocked.And(ref Flags, ~flag);
}

private static uint GetTrackerCount(ulong c)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ulong, long>(ref location1), (long)value);

/// <summary>Bitwise "ands" two values of type <typeparamref name="T"/> and replaces the first value with the result, as an atomic operation.</summary>
/// <param name="location1">A variable containing the first value to be combined. The result is stored in <paramref name="location1"/>.</param>
/// <param name="value">The value to be combined with the value at <paramref name="location1"/>.</param>
/// <returns>The original value in <paramref name="location1"/>.</returns>
/// <exception cref="NullReferenceException">The address of <paramref name="location1"/> is a null pointer.</exception>
/// <exception cref="NotSupportedException">An unsupported <typeparamref name="T"/> is specified.</exception>
/// <typeparam name="T">
/// The type to be used for <paramref name="location1"/> and <paramref name="value"/>.
/// 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.
/// </typeparam>
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe T And<T>(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<T, byte>(location1);
while (true)
{
byte newValue = (byte)(current & Unsafe.BitCast<T, byte>(value));
byte oldValue = CompareExchange(
ref Unsafe.As<T, byte>(ref location1),
newValue,
current);
if (oldValue == current)
{
return Unsafe.BitCast<byte, T>(oldValue);
}
current = oldValue;
}
}

if (sizeof(T) == 2)
{
ushort current = Unsafe.BitCast<T, ushort>(location1);
while (true)
{
ushort newValue = (ushort)(current & Unsafe.BitCast<T, ushort>(value));
ushort oldValue = CompareExchange(
ref Unsafe.As<T, ushort>(ref location1),
newValue,
current);
if (oldValue == current)
{
return Unsafe.BitCast<ushort, T>(oldValue);
}
current = oldValue;
}
}

if (sizeof(T) == 4)
{
return Unsafe.BitCast<int, T>(
And(
ref Unsafe.As<T, int>(ref location1),
Unsafe.BitCast<T, int>(value)));
}

Debug.Assert(sizeof(T) == 8);
return Unsafe.BitCast<long, T>(
And(
ref Unsafe.As<T, long>(ref location1),
Unsafe.BitCast<T, long>(value)));
}
#endregion

#region Or
Expand Down Expand Up @@ -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<ulong, long>(ref location1), (long)value);

/// <summary>Bitwise "ors" two values of type <typeparamref name="T"/> and replaces the first value with the result, as an atomic operation.</summary>
/// <param name="location1">A variable containing the first value to be combined. The result is stored in <paramref name="location1"/>.</param>
/// <param name="value">The value to be combined with the value at <paramref name="location1"/>.</param>
/// <returns>The original value in <paramref name="location1"/>.</returns>
/// <exception cref="NullReferenceException">The address of <paramref name="location1"/> is a null pointer.</exception>
/// <exception cref="NotSupportedException">An unsupported <typeparamref name="T"/> is specified.</exception>
/// <typeparam name="T">
/// The type to be used for <paramref name="location1"/> and <paramref name="value"/>.
/// 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.
/// </typeparam>
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe T Or<T>(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<T, byte>(location1);
while (true)
{
byte newValue = (byte)(current | Unsafe.BitCast<T, byte>(value));
byte oldValue = CompareExchange(
ref Unsafe.As<T, byte>(ref location1),
newValue,
current);
if (oldValue == current)
{
return Unsafe.BitCast<byte, T>(oldValue);
}
current = oldValue;
}
}

if (sizeof(T) == 2)
{
ushort current = Unsafe.BitCast<T, ushort>(location1);
while (true)
{
ushort newValue = (ushort)(current | Unsafe.BitCast<T, ushort>(value));
ushort oldValue = CompareExchange(
ref Unsafe.As<T, ushort>(ref location1),
newValue,
current);
if (oldValue == current)
{
return Unsafe.BitCast<ushort, T>(oldValue);
}
current = oldValue;
}
}

if (sizeof(T) == 4)
{
return Unsafe.BitCast<int, T>(
Or(
ref Unsafe.As<T, int>(ref location1),
Unsafe.BitCast<T, int>(value)));
}

Debug.Assert(sizeof(T) == 8);
return Unsafe.BitCast<long, T>(
Or(
ref Unsafe.As<T, long>(ref location1),
Unsafe.BitCast<T, long>(value)));
}
#endregion

#region MemoryBarrier
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Private.Uri/src/System/Uri.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Flags, ulong>(ref _flags), (ulong)flags);
Interlocked.Or(ref _flags, flags);
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Private.Uri/src/System/UriScheme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uri.Flags, ulong>(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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Private.Uri/src/System/UriSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uri.Flags, ulong>(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)
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Threading/ref/System.Threading.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(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)]
Expand Down Expand Up @@ -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<T>(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; }
Expand Down
Loading
Loading