-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Changed internal value to readonly for primitive types #18645
Conversation
@@ -226,7 +226,7 @@ public bool Equals(double obj) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] // 64-bit constants make the IL unusually large that makes the inliner to reject the method | |||
public override int GetHashCode() | |||
{ | |||
var bits = Unsafe.As<double, long>(ref m_value); | |||
var bits = BitConverter.DoubleToInt64Bits(m_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.
This will make the GetHashCode method significantly slower, I think.
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.
Really? The BitConverter.Int64BitsToDouble() method is simply:
return *((long*)&value); |
Which seems more straightforward than Unsafe.As(). Any way to review the generated assembly?
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.
Which seems more straightforward than Unsafe.As().
It's not as Int64BitsToDouble
will result in the double
value being loaded as double
, stored to a temporary and then loaded again as long
. The Unsafe.As
variant simply converts this
(which is basically a ref
in the case of structs) from ref double
to ref long
, that is a no-op.
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.
@mikedn Can you suggest how to get Unsafe.AsRef to work? I couldn't figure it out.
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.
Side questions: Can the compiler optimize away the cast by promoting the temporary into a register? And is there a X86/X64 instruction that can bit-copy from a floating point XMM register to a general purpose register?
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.
@jkotas I made the change to Unsafe.cs; however, I'm not exactly sure what to do with jitinterface.cpp and mscorlib.h.
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.
Here you go
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.
We'll need to check if this approach doesn't impact code gen as well. Sometimes the JIT has problems with multiple calls - even if they are inlined it may still spill trees and introduce new local variables.
I'll try to take a look later today.
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.
Works fine, the JIT generates identical IR for both old and new implementations.
And it turns out that the original As
call does result in a temporary variable being introduced. Fortunately it has no impact on the generated code.
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.
@mikedn Thanks for taking a look!
@jkotas Should perhaps someone from Roslyn take a look as well? I vaguely remember a comment indicating that Roslyn treats primitive types as |
It does in some cases, but not all cases. #18138 (comment) is an example where this makes a difference. |
@jaredpar Do you see any Roslyn issues with marking primitive types as readonly? |
No. We already consider such types to be CC @OmarTawfik |
[Intrinsic] | ||
[NonVersionable] | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static ref T AsRef<T>(in T source) |
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 understand this is in the Unsafe
class hence it's unsafe. I want to emphasize though that this operation is not just about violating readonly
semantics. It actually can cause some pretty severe safety issues. Consider the following code as an example:
void Example(in object o) {
Unsafe.AsRef(in o) = new object(); // really dangerous
}
void M(string[] array) {
object[] objArray = array;
Example(in objArray[0]);
}
Hence this operator allows writing objects that violate our basic type safety into locations. It's not just about violating readonly
.
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.
Right. S.R.CS.Unsafe is a very sharp knife that lets introduce pretty severe safety issue if used incorrectly. This particular API shipped already. This change is just adding internal clone of the public API for internal CoreLib consumption.
@tgiphil Could you please create a matching PR in corefx repo to update the reference assembly https://github.com/dotnet/corefx/tree/master/src/System.Runtime/ref ? (It will not build successfully until this change propagated to CoreFX - it is fine for the PR to just sit there until that happens.) |
PR at CoreFX: dotnet/corefx#30717 Please review. |
Changed internal value to readonly for primitive types Commit migrated from dotnet/coreclr@eeb9e89
See PR #18138 for more information.