Open colejohnson66 opened 11 months ago
Ideally, this would just be Read
and Write
, but because C# does not support overloading on generic constraints, this would interfere with the existing T Read<T>(ref T) where T:class
and void Write<T>(ref T, T) where T:class
methods
These APIs can be implemented with sizeof(T)
dispatch rather than typeof(T).GetEnumUnderlyingType()
:
static unsafe T ReadEnum<T>(ref T location)
where T : struct, Enum
{
T result;
#pragma warning disable 8500
if (sizeof(T) == sizeof(byte))
*(byte*)&result = Volatile.Read(ref Unsafe.As<T, byte>(ref location));
else if (sizeof(T) == sizeof(ushort))
*(ushort*)&result = Volatile.Read(ref Unsafe.As<T, ushort>(ref location));
else if (sizeof(T) == sizeof(uint))
*(uint*)&result = Volatile.Read(ref Unsafe.As<T, uint>(ref location));
else if (sizeof(T) == sizeof(ulong))
*(ulong*)&result = Volatile.Read(ref Unsafe.As<T, ulong>(ref location));
else
throw new NotSupportedException();
return result;
#pragma warning restore 8500
}
Which provides much nicer codegen and doesn't rely on the very recent (.NET 8) GetEnumUnderlyingType
optimization, so you get the nice codegen on .NET 7 (and probably Mono) too.
Didn't even look at codegen, or consider optimizations. It was just a proof-of-concept, but you're right. However, your usage of pointer magic to write the result (instead of Unsafe.As
to read it) makes no difference to codegen. The only important part is using sizeof
. It's all optimized down to:
; bytes
movzx eax, byte ptr [rcx]
ret
; words
movzx eax, word ptr [rcx]
cwde
ret
; double-words
mov eax, [rcx]
ret
; quad-words
mov rax, [rcx]
ret
Related https://github.com/dotnet/runtime/issues/65184
However, your usage of pointer magic to write the result (instead of
Unsafe.As
to read it) makes no difference to codegen.
Yeah, that was just an attempt to make the implementation smaller (should have a little less IL too).
Tagging subscribers to this area: @mangod9 See info in area-owners.md if you want to be subscribed.
Author: | colejohnson66 |
---|---|
Assignees: | - |
Labels: | `api-suggestion`, `area-System.Threading`, `untriaged`, `needs-area-label` |
Milestone: | - |
Background and motivation
Normally, one would use
Volatile.Read
andVolatile.Write
to handle interlocking of numeric values between tasks. Sometimes it's beneficial to use enums in these cases, but we are prevented from doing so due to limitations in the API. Currently, one must cast their enum values back and forth when reading and writing:Having overloads that take enums would be beneficial in these cases to avoid issues that occur when casting enum values (such as using incorrect numeric or enum types). For example, a
ulong
backed enum must be cast to eitherlong
orulong
lest you lose 32 bits of data. Casting toint
above would throw away such data.API Proposal
In each function,
sizeof(TEnum)
would be used to determine the size of the enum. ThenUnsafe.As
and normalVolatile
methods would be used to massage out the value. For example, an implementation ofReadEnum
that only handled single-byte-backed enums would look likeAPI Usage
Alternative Designs
Alternatively,
Volatile.Read<T>(ref T)
andVolatile.Write<T>(ref T, T)
can have their generic constraint removed and replaced with runtime checks (a la #99205 and #100842):If
T : class
, it operates the same. However, ifT : struct, Enum
, it would now operate as proposed. This is a non-breaking change, as it relaxes a generic constraint to allow previously non-compilable code.It could be extended even further to work on any unmanaged struct that's eight bytes or fewer. In other words,
Volatile.Read(ref myFourByteStruct)
would now be legal.This alternate design is probably undesirable. Despite being non-breaking, it can be risky. Say I have a four-byte struct and use
Volatile.(Read|Write)
on it. If, in the future, I change it to be, say, 12 bytes large, the code will still compile without warnings, but would always throw at runtime. If this alternate design is chosen, a source analyzer should be included. It would detectT
types that are greater than eight bytes and raise a compile-time warning.