Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Changed IntPtr's internal value to readonly (and therefore immutable) #18138

Closed
wants to merge 9 commits into from

Conversation

tgiphil
Copy link

@tgiphil tgiphil commented May 26, 2018

No description provided.

@benaadams
Copy link
Member

Should it be a readonly struct then?
readonly struct IntPtr

@tgiphil
Copy link
Author

tgiphil commented May 26, 2018

@benaadams Oh yeah! That's a new feature in C# v7.2.

@@ -23,7 +23,7 @@ public struct IntPtr : IEquatable<IntPtr>, ISerializable
// See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details.
// Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools.
// Get in touch with the diagnostics team if you have questions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you can tell from these comments, the primitive types are treated in special way by tools our there. I wonder what all can break with this change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me too. Would the existing automated test suites catch this if it were a breaking change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing tests may not be sufficient to catch the issues. Changes like this introduce new combinations that the existing tests were not designed to exercise.

We have tried to mark Nullable<T> as readonly. All existing tests were green on the change. But it had to be rolled back because of it was identified as breaking change later. dotnet/corefx#24997 (comment)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! Although it appears System.Nullable may have been a special case.

However, I defer to your more qualified judgement in dropping this PR.

@jkotas
Copy link
Member

jkotas commented May 26, 2018

What are the benefits of this change? If this change has any significant benefits, it should be done for all primitive types (Int*, Float, Double, Boolean, Char) for consistency and we need to understand the breaking potential of this change.

Also, a matching PR needs to be made in corefx to make the same change in contracts.

@tgiphil
Copy link
Author

tgiphil commented May 28, 2018

@jkotas I made the change for all the other primitive types, except Double and Single because their implementation of GetHashCode() passes a reference to the internal value to Unsafe.As().

This change is mostly semantic sugar as it would add an immutable constraint to the internal type. (+1) The field would also be marked initonly in the IL metadata. If the compiler has any optimizations dependent the initonly field, this may unlock an compiler optimization or two. (+1).

@jkotas
Copy link
Member

jkotas commented May 28, 2018

compiler has any optimizations dependent the initonly field, this may unlock an compiler optimization or two

Do you have any real example of optimization that it unlocks?

@tgiphil
Copy link
Author

tgiphil commented May 28, 2018

@jkotas I don't have any real examples. In fact, I was reviewing the source code to better understand how the backend compiler could optimize away the load for static primitive types (like, address == UIntPtr.Zero). It seems the compiler could reason the load would be unnecessary because in this case UIntPtr.Zero static field is (should be) immutable and a fixed value (zero). In fact, why even store the field in memory when the value must always be zero.

Maybe one of the backend compiler gurus can provide some input?

@jkotas
Copy link
Member

jkotas commented May 28, 2018

The JIT does the IntPtr.Zero optimization you are describing already. It treats it as intrinsic

.

@tgiphil
Copy link
Author

tgiphil commented May 28, 2018

@jkotas Thanks! I missed that --- I thought [Intrinsic] were only for methods & properties.

@jkotas
Copy link
Member

jkotas commented Jun 25, 2018

This does actually allow C# to generate more efficient IL in some cases. For example:

readonly struct Test
{
    public readonly uint x;

    static void Method() {
          Test x = default;
          x.x.ToString();
    }
}

Without this change, there will be a temp created on the stack that to store the field x. With this change, the temp goes away.

@jkotas
Copy link
Member

jkotas commented Jun 25, 2018

I think this is interesting change to take. I would like it to be enabled uniformly for all primitive types, including signed types (Int32, Int64, etc.) and floating point types (Single, Double).

@tgiphil Are you still interested in working on this? Thank you!

@jkotas
Copy link
Member

jkotas commented Jun 25, 2018

except Double and Single because their implementation of GetHashCode() passes a reference to the internal value to Unsafe.As()

We can add Unsafe.AsRef(in T source) to CoreLib to fix this problem.

@tgiphil
Copy link
Author

tgiphil commented Jun 25, 2018

@jkotas Yes, I'd be interested in working on this. I may need help with floating point types (Single, Double) because their implementation of GetHashCode() passes a reference to the internal value to Unsafe.As().

@tannergooding
Copy link
Member

@tgiphil, there should be an Unsafe.AsRef method which takes in T and strips away readonly (returning ref T) just for cases like this, where the value isn't actually mutated. (there might also be an overload of As that just takes in, but I don't recall).

@tgiphil
Copy link
Author

tgiphil commented Jun 26, 2018

@jkotas The rest of the primitive types have been updated. The Unsafe.As calls were avoided by using BitConverter.DoubleToInt64Bits and BitConverter.SingleToInt32Bits methods instead.

@jkotas
Copy link
Member

jkotas commented Jun 26, 2018

DoubleToInt64Bits, SingleToInt32Bits

What does that do to the GetHashCode performance?

@jkotas
Copy link
Member

jkotas commented Jun 26, 2018

Also, could you please rebase or merge with current master to resolve the conflicts?

tgiphil added 3 commits June 25, 2018 21:55
# Conflicts:
#	src/System.Private.CoreLib/shared/System/Boolean.cs
#	src/System.Private.CoreLib/shared/System/Byte.cs
#	src/System.Private.CoreLib/shared/System/Char.cs
#	src/System.Private.CoreLib/shared/System/UInt16.cs
#	src/System.Private.CoreLib/shared/System/UInt32.cs
#	src/System.Private.CoreLib/shared/System/UInt64.cs
@tgiphil
Copy link
Author

tgiphil commented Jun 26, 2018

The rebase didn't work out as expected. I opened up another PR instead:

#18645

@jkotas
Copy link
Member

jkotas commented Jun 26, 2018

Superseded by #18645

@jkotas jkotas closed this Jun 26, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants