-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Interlocked.CompareExchange for byte/short/sbyte/ushort #64658
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @mangod9 Issue DetailsBackground and motivationWe have a few flags fields in the compiler that can be set by multiple threads, used as part of our symbol models. As we avoid locks whenever possible, we generally use a loop on API Proposalnamespace System.Threading
{
public class Interlocked
{
public byte CompareExchange(ref byte location, byte value, byte comparand);
public sbyte CompareExchange(ref sbyte location, sbyte value, sbyte comparand);
public short CompareExchange(ref short location, short value, short comparand);
public ushort CompareExchange(ref ushort location, ushort value, ushort comparand);
}
} API Usage public static bool Set(ref byte flags, byte toSet)
{
byte oldState, newState;
do
{
oldState = flags;
newState = oldState | toSet;
if (newState == oldState)
{
return false;
}
}
while (Interlocked.CompareExchange(byte flags, newState, oldState) != oldState);
return true;
} Alternative DesignsNo response RisksNo response
|
Also CC. @stephentoub This is something that both x86/x64 (
|
I have no problem with us adding CompareExchange overloads for byte/short/etc. if every architecture we target and envision targeting supports them. Same for overloads of other existing APIs we have, e.g. Increment, Decrement, etc.
We discussed this in: |
A slight digression from the topic of the issue, but related to it. |
@333fred Can you add a A |
That's #45103.
I don't think we should do that. |
@stephentoub is there anything blocking this suggestion? Can we tag it for review? |
That's up to @kouvel. |
I don't see any issues with this either, marked as ready-for-review |
namespace System.Threading
{
public static class Interlocked
{
public static byte CompareExchange(ref byte location1, byte value, byte comparand);
public static sbyte CompareExchange(ref sbyte location1, sbyte value, sbyte comparand);
public static short CompareExchange(ref short location1, short value, short comparand);
public static ushort CompareExchange(ref ushort location1, ushort value, ushort comparand);
public static byte Exchange(ref byte location1, byte value);
public static sbyte Exchange(ref sbyte location1, sbyte value);
public static short Exchange(ref short location1, short value);
public static ushort Exchange(ref ushort location1, ushort value);
}
} |
Does this API require JIT-changes? |
There are multiple possible implementation strategies. It can either be implemented in the runtimes in C/C++ and/or it can be must-expand intrinsic in the JIT. |
Even if an architecture doesn't support these operations, couldn't they be implemented as CompareExchange loops? public static short CompareExchange(ref short location, short value, short comparand)
{
ref int locationAsInt = ref Unsafe.As<short, int>(ref location);
int newValueAsInt = 0;
ref short newValueLocation = ref Unsafe.As<int, short>(ref newValueAsInt);
int initialValueAsInt;
short initialValue;
do
{
initialValueAsInt = Volatile.Read(ref locationAsInt);
initialValue = Unsafe.As<int, short>(ref initialValueAsInt);
if (initialValue != comparand)
{
return initialValue;
}
newValueAsInt = initialValueAsInt;
Volatile.Write(ref newValueLocation, value);
} while (Interlocked.CompareExchange(ref locationAsInt, newValueAsInt, initialValueAsInt) != initialValueAsInt);
return initialValue;
} Of course I realize this could be quite slow if the short's location isn't on a 4-byte address. The int location could be shifted to the floor of the nearby 4-byte address (using pointers instead of [Edit] Although, I would find it strange for a new architecture to support working with small values and not also support working with them in an Interlocked fashion. |
@timcassell |
Ah yes, good catch.
No, that would change the behavior to compare exchange if not equal instead of compare exchange if equal. Also, that check would be removed to implement |
Indeed, you are right. Sorry for the false alarm! |
In your code, if the byte/short/etc. is just before memory that's not accessible, such that extending by a few more bytes steps over that boundary, this could AV / seg fault.
Which is why we don't want to implement these without the right hardware support. Otherwise someone "optimizing" their code by updating it from, say, using an int field to instead using a byte field is actually deoptimizing it. |
Yes, I realized that, and shifting the pointer like I mentioned would solve that as well. But that's not the point. That was just a proof-of-concept to show that it could be implemented without hardware support, if necessary.
It seems that all architectures that .Net currently supports have hardware support, so this is a non-issue. And I doubt future architecture wouldn't support it. But, hypothetically, if .Net does support a future architecture that doesn't have hardware support, would you prefer to throw a NotSupportedException, or have a slow implementation? |
My point was we're not going to add a temporary implementation based on such a loop. |
Perhaps you meant |
Right, my bad. |
What do you think? @terrajobst @stephentoub |
It's trivial for someone to implement those themselves in terms of the proposed ones. That said, bool could be a reasonable addition, just because it's so common. The problem is it's a slippery slope with all the different methods and all the different possible types; line needs to be drawn somewhere. Why are you asking @deeprobin? |
@stephentoub Oh I thought something like that always has to be strictly re-reviewed. |
It does if it were to be included.
I'm concerned this may be too complex. What is your plan? |
You are right. I took a look at the whole thing. I would probably have to include instructions like CMPXCHG8B & CMPXCHG16B, which I would still consider feasible, but the ARM implementation would worry me because I haven't dealt with it yet. Then the issue is better for someone else :D |
I could really use an Interlocked.CompareExchange |
The only issue with that is that a bool with internal representation of 2 would be considered different from one represented as 1. |
I don't think that's an issue. The [Edit] Also, you were the one who suggested adding |
The issue is that it introduces subtle and "unexpected" behavior that many users have to deal with. This makes it a very hard to track down bug when the issue does crop up. |
Once |
How is that any different than |
I don't particularly care whether we add a bool-based overload, but how is this concern different from normal comparisons? e.g. using System.Runtime.CompilerServices;
byte one = 1;
byte two = 2;
bool b1 = Unsafe.As<byte, bool>(ref one);
bool b2 = Unsafe.As<byte, bool>(ref two);
Console.WriteLine(b1);
Console.WriteLine(b2);
Console.WriteLine(b1 == b2); prints true, true, and false. |
In the case of interlocked, it can lead to deadlocks and other subtle problems with complex multithreaded code that make it infinitely harder to diagnose. Having it be encountered as an edge case in scenarios like |
Yeah I've realized this afterwards. |
I don't personally see Interlocked adding to that complexity here. You can easily deadlock without it. And it's the introduction of non-canonical Boolean values that's the unsafeness, not a compare, whether atomic with an exchange or not. |
What is the current status of this? I considered implementing my own implementation in C++ and do an external call before I found this issue. |
It's scheduled for .NET 9 (which will ship next year). It won't be in .NET 8. @mangod9 @kouvel should this be labelled with help wanted? |
Implements Interlocked.CompareExchange/Exchange for byte/sbyte/short/ushort. Uses an intrinsic on CoreCLR x64 and ARM64 and fallbacks for other platforms. Fixes dotnet#64658.
* Implement Interlocked for small types. Implements Interlocked.CompareExchange/Exchange for byte/sbyte/short/ushort. Uses an intrinsic on CoreCLR x64 and ARM64 and fallbacks for other platforms. Fixes #64658. * Fix xarch assert * Update PalRedhawkInline.h * Update PalRedhawkInline.h * Update threads.c * Simplify NAOT handling * Fix build * Update atomic.h * Update atomic.h * Update atomic.h * Fix windows intrinsics * Update comutilnative.cpp * Fix Windows builds * Update icall-def.h * Fix icall-def.h * Improve tests * Update InterlockedTests.cs * Remove small type normalization * Try to fix the intrinsics * Fix ARM64 build * Try to fix XArch 66 prefix and ARM64 * Fix typo * Fix assert * Code cleanup * Update importercalls.cpp * Update importercalls.cpp * Extend small types properly * Add RISC-V asserts * c11 atomics interlocked ops for small types * Simplify Windows atomics * Restore NativeAOT fallbacks for other platforms * Add more tests * Format * Update Interlocked.cs * Fix tests and comment * Add emitOutputCV handling * Fix extension * Use a slightly better fix * More complete fix * Fix the fix * Fix more places * CR feedback * Fix corelib * Fix tests * Add using * Match NativeAOT #ifs * Update Interlocked.cs * Fix test projects * Fix tests, try linking with libatomic * Improve build test * Fix tests * Fix tests again * Remove libatomic Co-authored-by: Jan Kotas <jkotas@microsoft.com> * Cleanup, fix helper extension * Fix test corelib --------- Co-authored-by: Alexander Köplinger <alex.koeplinger@outlook.com> Co-authored-by: Aleksey Kliger <alklig@microsoft.com> Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Background and motivation
We have a few flags fields in the compiler that can be set by multiple threads, used as part of our symbol models. As we avoid locks whenever possible, we generally use a loop on
Interlocked.CompareExchange
, under the assumption it will succeed eventually or the value will have been computed and set by some other thread. However, becauseCompareExchange
only supportsint
,long
,uint
, andulong
integer values, these fields must be at least 1int
wide. This doesn't sound like much, but as the compiler needs to load many, many symbols, those few extra bits can add up. I'd like to be able to usebyte
s where possible for these values. I only really wantbyte
/ushort
overloads, but added the signed variants for completeness.API Proposal
API Usage
Alternative Designs
No response
Risks
No response
The text was updated successfully, but these errors were encountered: