-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Updating to a version of Roslyn that contains the NumericIntPtr feature #69627
Conversation
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @dotnet/area-system-runtime Issue DetailsFor .NET 7/C# 11, This updates to Roslyn v4.3.0-2.22270.2 which includes the relevant changes and updates There were a handful of Span/ROSpan tests that likewise required updating due to the constant being "too large" and now trigger the relevant warning that it may fail on 32-bit platforms. Otherwise, no other code is being touched in this PR. A separate PR can be put up that switches the BCL over to use the keyword but that is "less important" since it isn't required to ship nor will it otherwise impact codegen now.
|
CC. @jcouv as an FYI |
@@ -34,136 +33,125 @@ namespace System | |||
IMinMaxValue<nint>, | |||
ISignedNumber<nint> | |||
{ | |||
private readonly unsafe void* _value; // Do not rename (binary serialization) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) Do we want to keep the comment here, since we still support binary serialization (for now)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GrabYourPitchforks indicated the comment is out of date since we have an explicit serializer method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To expand on this a little bit, the ISerializable interface tells reflection-based serializers, "Stop! There's something special about my layout and you shouldn't perform your normal scrape-the-fields logic when serializing me. Instead, call GetObjectData and my special ctor, and I'll tell you how to serialize my own instances."
So as long as the type has ISerializable on it (or we add it), we have considerable leeway to change the internal layout of the type with regard to serialization. Debuggers (SOS) may need to be updated separately, though.
|
||
[NonVersionable] | ||
public static unsafe explicit operator IntPtr(long value) => | ||
new IntPtr(value); | ||
public static explicit operator nint(long value) => checked((nint)value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my education, how doesn't this result in an infinite loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same as for Int32
. Basically the C# compiler ignores all "user-defined operators" for the primitive types and uses its own "built-in" operators instead.
So the user-defined method basically only exists for "back-compat" and can only be invoked indirectly such as via reflection with the C# compiler just emitting conv.ovf.i
for checked((nint)value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a technical grasp on the logic behind all of these updates, but at a high level this change makes sense, and I trust that this is mostly mechanical. I recommend someone else take a look at some point, but I can approve for now to unblock you.
@@ -78,7 +78,7 @@ public static void SliceStartInt32Overflow() | |||
{ | |||
unsafe | |||
{ | |||
if (!AllocationHelper.TryAllocNative((IntPtr)ThreeGiB, out IntPtr memory)) | |||
if (!AllocationHelper.TryAllocNative(unchecked((nint)ThreeGiB), out IntPtr memory)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did IntPtr
used to behave as an unchecked nint
, hence this update being needed now that IntPtr
has different behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IntPtr
had no language support while nint
did. This meant that (IntPtr)TooLargeConstant
would issue no error while (nint)TooLargeConstant
would issue a error.
Now that IntPtr
/nint
are aliases and have all the same functionality, IntPtr
issues an error requiring you to acknowledge the potential overflow that existed here and use unchecked
.
GenAPI changes are here: dotnet/arcade#9421 |
{ | ||
if (destination.Length >= sizeof(nint_t)) | ||
{ | ||
nint_t value = BitConverter.IsLittleEndian ? (nint_t)(_value) : BinaryPrimitives.ReverseEndianness((nint_t)(_value)); | ||
nint_t value = (nint_t)_value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrectly resolved merge conflict? This seems to be undoing #69063
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. #69063 didn't touch these files, only System.Buffers.Binary.BinaryPrimitives
For writing the pattern looks to be the below, where-as the ternary expression is for reads.
if (!BitConverter.IsLittleEndian)
{
nint_t value = ReverseEndianness((nint_t)_value);
return MemoryMarshal.Write(destination, ref value);
}
return MemoryMarshal.Write(destination, ref _value);
I'd need to check how this plays into Unsafe.WriteUnaligned
, but more generally this should probably just use #if BIGENDIAN
and avoid the larger IL and branches altogether.
I'd suspect the "ideal" here is the following and I can log an issue to track validating this and doing the "optimal" thing:
#if BIGENDIAN
return Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), BinaryPrimitives.ReverseEndianness((nint_t)_value));
#else
return Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), _value);
#endif
… check the overflow exception
There are |
Updated the analyzer and the CompatibilitySuppressions files. Logged dotnet/sdk#25566 to ensure the analyzer correctly handles this scenario so we can remove the unnecessary suppressions. I did manually validate that the APIs do still exist in the packages. |
…ected call on Mono
if (ex is TargetInvocationException) | ||
{ | ||
// RyuJIT throws TargetInvocationException wrapping an OverflowException | ||
// while Mono directly throws the OverflowException | ||
ex = ex.InnerException; | ||
} | ||
Assert.IsType<OverflowException>(ex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out Mono and RyuJIT have different behavior for how the exception is thrown here.
|
||
if (Size == 4 && (l > int.MaxValue || l < int.MinValue)) | ||
#if TARGET_32BIT | ||
if ((value > int.MaxValue) || (value < int.MinValue)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A good example of where #46259 (comment) and #54439 would come in handy. Would also allow you to get rid of the ifdef.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd prefer the ifdef here anyways. This isn't exactly perf critical, but it is one of the "core types" in the BCL and we have the infrastructure to minimize the IL here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For libraries that aren't corelib and aren't getting built uniquely for each OS/Architecture, 100% agree
VerifyPointer(new UIntPtr(i), i); | ||
VerifyPointer((UIntPtr)i, i); | ||
VerifyPointer(new nuint(i), i); | ||
VerifyPointer(i, i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is testing the correct thing now. (And in fact it may have been broken during the earlier nint / IntPtr consolidation.) The C# compiler in netcore emits a conv.u instruction, whereas the compiler in netfx would've emitted a call (op explicit) instruction. This line presumably intends to test our implementation of the explicit operator. We might even need to drop down to reflection to call it now, similar to what you've done in other test methods here. Alternatively, just kill it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line presumably intends to test our implementation of the explicit operator. We might even need to drop down to reflection to call it now, similar to what you've done in other test methods here. Alternatively, just kill it.
Given that this test is for Ctor_UInt
and there is a separate test covering explicit conversions to/from, I took this as just wanting to separately validate that the constructor returns the same value as a cast and not as explicitly wanting to compare new nuint
vs the user-defined nuint op_Explicit(uint)
.
It's definitely possible that it could have been trying to validate the latter instead, but the TestExplicitCast
test is already covering this exact value anyways so it doesn't buy much to do it.
I'd be amenable to removing it if you'd prefer.
For .NET 7/C# 11,
IntPtr
andnint
are now aliases of each other in the same way thatInt32
andint
are.This updates to Roslyn v4.3.0-2.22270.2 which includes the relevant changes and updates
IntPtr
/UIntPtr
to take advantage of the feature accordingly. For the IntPtr/UIntPtr tests, they now use reflection to ensure that the user-defined operators are still called and validated where relevant. They have likewise been updated to otherwise take advantage of the feature.There were a handful of Span/ROSpan tests that likewise required updating due to the constant being "too large" and now trigger the relevant warning that it may fail on 32-bit platforms. Otherwise, no other code is being touched in this PR. A separate PR can be put up that switches the BCL over to use the keyword but that is "less important" since it isn't required to ship nor will it otherwise impact codegen now.