You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add generic And<T> and Or<T> methods to System.Threading.Interlocked (#120978)
## Summary
This PR adds generic versions of the `And` and `Or` methods to
`System.Threading.Interlocked` as specified in API proposal #114568.
## Changes
- [x] Add generic `And<T>(ref T, T) where T : struct` method
- [x] Add generic `Or<T>(ref T, T) where T : struct` method
- [x] Support integer primitive types and enums backed by integer types
- [x] Reject float/double types and floating-point backed enums
- [x] Add appropriate error handling and messages
- [x] Update reference assembly
- [x] Add comprehensive theory-based tests (40+ test methods with 100+
test cases)
- [x] Replace existing usages of `Interlocked.And/Or(ref Unsafe.As...)`
with new generic API
- [x] Restrict JIT intrinsic expansion to TYP_INT and TYP_LONG only
- [x] Add TODO comment for small integer intrinsic support
- [x] Update XML documentation to clarify floating-point restrictions
- [x] Simplify validation logic to single-level if statement
- [x] Security checks (CodeQL - no issues)
## Implementation Details
- **For 1-2 byte types** (`byte`, `sbyte`, `short`, `ushort`): Uses
CompareExchange-based loop (managed fallback)
- **For 4-byte types** (`int`, `uint`): Delegates to int And/Or with
bitcast (JIT intrinsic)
- **For 8-byte types** (`long`, `ulong`): Delegates to long And/Or with
bitcast (JIT intrinsic)
- **Float/Double**: Rejected via type checks
- **Floating-point backed enums**: Rejected via
`Type.GetEnumUnderlyingType()` check (JIT-foldable)
- **JIT Integration**: Modified to only expand intrinsic for TYP_INT and
TYP_LONG; other types fall back to managed
- **Pattern**: Follows same approach as generic `CompareExchange<T>`
## JIT Changes
Modified `importercalls.cpp` to restrict intrinsic expansion:
- Only `TYP_INT` and `TYP_LONG` are expanded as atomic operations
- All other types (byte, short, float, double) fall back to managed
CompareExchange loops
- Uses `callType` consistently for type checks
- Added TODO comment: "Implement support for XAND/XORR with small
integer types (byte/short)"
- Ensures correct behavior across all supported primitive types and
enums
## Type Rejection
The methods reject:
- Non-primitive, non-enum types
- Floating-point primitives (float, double)
- Enums backed by floating-point types (via
`Type.GetEnumUnderlyingType()`)
The validation check is a single-level if statement with proper
indentation:
```cs
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);
}
```
## Documentation
The XML documentation clearly states the restrictions:
```cs
/// <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>
```
## Test Coverage
Converted all tests to `[Theory]` with `[InlineData]` covering:
- **Multiple test cases per type**: 5-10 values each including edge
cases
- **Negative values**: Comprehensive coverage for all signed types
(sbyte, short, int, long)
- **Edge cases**: Min/max values, zero, all-ones, alternating bit
patterns
- **Float/Double/Half NotSupportedException tests**: Verify rejection of
floating-point types
- **Null reference exceptions**: Separate tests for all types
- **Unsupported types**: Tests for non-primitive value types
## Usages Replaced
Replaced 7 call sites across the codebase:
- `System.Private.CoreLib/ComWrappers.cs` (2 calls)
- `System.Net.Http/CacheControlHeaderValue.cs` (2 calls)
- `System.Private.Uri/Uri.cs` (1 call)
- `System.Private.Uri/UriSyntax.cs` (1 call)
- `System.Private.Uri/UriScheme.cs` (1 call)
All usages now use the cleaner generic API without `Unsafe.As` casts.
## Test Results
✅ All 705 System.Threading tests pass (0 errors, 0 failures)
## Security
✅ CodeQL analysis passed with no issues
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
> We have the following public APIs in `System.Threading.Interlocked`
class:
>
> ```cs
> public static int Or(ref int location1, int value)
> public static uint Or(ref uint location1, uint value)
> public static long Or(ref long location1, long value)
> public static ulong Or(ref ulong location1, ulong value)
>
> public static int And(ref int location1, int value)
> public static uint And(ref uint location1, uint value)
> public static long And(ref long location1, long value)
> public static ulong And(ref ulong location1, ulong value)
> ```
> We want to add their generic versions:
> ```
> public static T Or<T>(ref T location1, T value) where T : struct;
> public static T And<T>(ref T location1, T value) where T : struct;
> ```
> for the same types + enums backed by those types.
>
> Similar to what we did for CompareExchange in
#104558.
> NOTE: this will require a bit of JIT work, e.g.
NI_System_Threading_Interlocked_Or and
NI_System_Threading_Interlocked_And
>
> Create a PR in dotnet/runtime to add that. Effectively, implement this
API proposal: #114568
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com>
Co-authored-by: Egor Bogatov <egorbo@gmail.com>
/// <summary>Bitwise "ands" two values of type <typeparamref name="T"/> and replaces the first value with the result, as an atomic operation.</summary>
639
+
/// <param name="location1">A variable containing the first value to be combined. The result is stored in <paramref name="location1"/>.</param>
640
+
/// <param name="value">The value to be combined with the value at <paramref name="location1"/>.</param>
641
+
/// <returns>The original value in <paramref name="location1"/>.</returns>
642
+
/// <exception cref="NullReferenceException">The address of <paramref name="location1"/> is a null pointer.</exception>
643
+
/// <exception cref="NotSupportedException">An unsupported <typeparamref name="T"/> is specified.</exception>
644
+
/// <typeparam name="T">
645
+
/// The type to be used for <paramref name="location1"/> and <paramref name="value"/>.
646
+
/// This type must be an integer primitive type or an enum type backed by an integer type.
647
+
/// Floating-point types (float, double) are not supported.
0 commit comments