Skip to content

Conversation

@huoyaoyuan
Copy link
Member

@huoyaoyuan huoyaoyuan commented Jan 11, 2025

Closes #94134. IEquatable<T> included.

@ghost
Copy link

ghost commented Jan 11, 2025

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, 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.

1 similar comment
@ghost
Copy link

ghost commented Jan 11, 2025

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, 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.

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 11, 2025
Copy link
Contributor

@Sergio0694 Sergio0694 left a comment

Choose a reason for hiding this comment

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

Left a few notes based on previous discussions 🙂
Thank you for taking this one!

get
{
IntPtr handle = _handle;
GCHandle.ThrowIfInvalid(_handle);
Copy link
Contributor

Choose a reason for hiding this comment

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

All these checks should be removed. Using any instance method on an instance that's not allocated is explicitly not supported. As long as we can throw an exception (eg. this should throw NRE) so the behavior is still consistent, that's sufficient.

Copy link
Member Author

Choose a reason for hiding this comment

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

As long as we can throw an exception (eg. this should throw NRE)

This is the questionable part - the current implementation uses FCall in some cases, and the ability to throw exception from FCall will soon be removed. In other words, no NRE can be thrown, at least for some members.

With that said, providing invalid value via FromIntPtr will still cause hard AV in unmanaged code.

Copy link
Contributor

Choose a reason for hiding this comment

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

At least for the getter, on CoreCLR and NAOT this should just be *(T*)_handle, which should already throw NRE implicitly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe an implicit read (_ = *(byte*)value) can be applied with lowest overhead? I'm not worried about garbage value from FromIntPtr, but make new GCHandle<T>{ Target = obj } crashing doesn't look good.

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you mean by implicit read? You need to read anyway, since you're returning a value. What I'm saying is the whole getter should literally just be get => *(T*)_handle. That will automatically NRE if handle is null, which is all we need.

Copy link
Member Author

Choose a reason for hiding this comment

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

I mean adding a read in managed code in setter. Currently the entire setter is executed in unmanaged code and will cause AV, which won't be caught as NRE.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see what you mean, I thought you were talking about the other accessor. That would seem fine to me, but I'll defer to Jan and others to confirm 🙂

@MihaZupan MihaZupan added this to the 10.0.0 milestone Jan 11, 2025
IntPtr handle = _handle;
GCHandle.ThrowIfInvalid(handle);

return GCHandle.AddrOfPinnedObjectFromHandle(_handle);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this expected to follow GCHandle in special casing strings and arrays here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Memory<T>.Pin and fixed are all following the contract to return the address of first element. Not following the contract would require strong justification.

Copy link
Member

Choose a reason for hiding this comment

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

This method should be only used for non-array non-string blittable types. Memory<T>.Pin and fixed do not work for non-array non-string blittable types.

I think it would make sense to make it work just for the non-array non-string blittable types and document the behavior as undefined to avoid unnecessary overhead. The overall goal with these new GCHandle types was to eliminate unnecessary overheads.

The original intention with this method was to provide a replacement for GCHandle.AddrOfPinnedObject. I think the method has different enough name to have different behavior.

#nullable disable // Nullable oblivious because no covariance between PinnedGCHandle<T> and PinnedGCHandle<T?>
this PinnedGCHandle<T[]> handle)
#nullable restore
=> (T*)handle.GetAddressOfObjectData();
Copy link
Contributor

Choose a reason for hiding this comment

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

Should those be implemented separately here to avoid runtime checks GCHandle does for performance?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. Yes the check can't be eliminated here.

/// <param name="target">The object that uses the <see cref="GCHandle{T}"/>.</param>
public GCHandle(T target)
{
_handle = GCHandle.InternalAlloc(target, GCHandleType.Normal);
Copy link
Member

Choose a reason for hiding this comment

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

Since we rely on IntPtr.Zero elsewhere in the implementation having special meaning, worth asserting here that _handle != IntPtr.Zero?

Copy link
Member Author

Choose a reason for hiding this comment

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

Should the assert go to InternalAlloc since there are more types depending on this?

@huoyaoyuan
Copy link
Member Author

Any further review on this?
I'm going to OOF for holidays starting from next Wednesday. Please feel free to directly update the branch if you need to drive this.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

LGTM. Thank you!

@jkotas jkotas merged commit 7b5f7f0 into dotnet:main Feb 13, 2025
143 of 149 checks passed
@huoyaoyuan huoyaoyuan deleted the gchandle branch February 13, 2025 02:47
@github-actions github-actions bot locked and limited conversation to collaborators Mar 15, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: GCHandle<T> (like GCHandle, but this time it's great™️)