-
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
[API Proposal]: System.Runtime.CompilerServices.Unsafe.AsPtrRef<T, U>(ref T* source) : ref U #62342
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
I mean of course I like this proposal, a couple of small questions though 😄
namespace System.Runtime.CompilerServices
{
public static class Unsafe
{
public static ref U As<T, U>(ref T* source)
where T : unmanaged
where U : unmanaged;
}
}
namespace System.Runtime.CompilerServices
{
public static class Unsafe
{
public static ref U* AsPointerRef<T, U>(ref T source)
where T : unmanaged
where U : unmanaged;
}
} Other than that I agree this would unblock several scenarios while language support isn't there. Additionally, worth mentioning, @AndyAyersMS is looking into letting the JIT inline function pointer calls, which would allow doing this (or, defining these APIs) entirely in C# with no need for new APIs, with the same final codegen). They'd be: public static ref U As<T, U>(ref T* source)
where T : unmanaged
where U : unmanaged
{
return ref
((delegate*<ref T*, ref U>)
(delegate*<ref byte, ref byte>)
&Unsafe.As<byte, byte>)(ref source);
}
public static ref U* AsPointerRef<T, U>(ref T source)
where T : unmanaged
where U : unmanaged
{
return ref
((delegate*<ref T, ref U*>)
(delegate*<ref byte, ref byte>)
&Unsafe.As<byte, byte>)(ref source);
} |
@Sergio0694 I'm fine with whatever the chosen name is :) |
Tagging subscribers to this area: @dotnet/area-system-runtime-compilerservices Issue DetailsBackground and motivationI'm told that something similar to this has been suggested before but it was decided that it was "too niche." Well, I'm finding that I could really use this! Without it, it's impossible to do certain operations on variables or fields that are pointers. No From what I can tell, this is the key method that's needed to bridge the gap between pointers and generics. No need for adding many other pieces of support in the language, compiler, runtime, etc. Just let us temporarily reinterpret a In interop code, it's more idiomatic and less kludgey to just use pointers, and it's unsavory to have to switch everything to pointer-wrapping structs just because pointers aren't compatible with many things in the compiler or the framework. Once you have a field or variable that's a With this I can do With help from others (esp. @jakobbotsch) I was able to get a prototype of this and it does work. Not having this in the runtime is very inconvenient, but not completely blocking, as I could create a nuget package. However, having an assembly and nuget package for the sake of 1 method is a little heavy. cc @Sergio0694 @jakobbotsch @tannergooding who were part of the discussion in Discord API Proposalnamespace System.Runtime.CompilerServices
{
public static class Unsafe
{
// This name was chosen in part so it does not sort next to other methods like AsRef,
// therefore less probability it could be accidentally used (via auto-complete or etc.).
// sizeof(U) must equal sizeof(T*)
public static ref U AsPtrRef<T, U>(ref T* source)
where T : unmanaged
where U : unmanaged
// The inverse operation is needed as well, to convert from ref U back to ref T*
public static ref U* AsPtrRef<T, U>(ref T source)
where T : unmanaged
where U : unmanaged
// Might want to have `ref T**` versions as well, up to a reasonable arity. `ref T***` perhaps, but `ref T*******` is a bit much. `T***` does _very occasionally_ pop up in native interop code (pointer to 2-dimensional array).
}
} The IL for this is pretty straightforward, it's just
API Usage
Alternative DesignsNo response RisksThe naming of the method needs to be chosen very carefully. If the non-pointer type being converted to/from is not at least pointer-sized, bad things can happen. But, this is
|
cc @DaZombieKiller who was also part of the discussion in Discord |
For the time being (or longer), I've published a NuGet package that enables this sort of thing, https://github.com/rickbrew/PointerToolkit/ . I use InlineIL.Fody to generate the methods. |
Background and motivation
I'm told that something similar to this has been suggested before but it was decided that it was "too niche."
Well, I'm finding that I could really use this! Without it, it's impossible to do certain operations on variables or fields that are pointers. No
Interlocked.Exchange
, no nothing. Generics don't work with pointers, but they do work with pointer-size structs that simply wrap the pointer (e.g.Ptr<T>
which is just astruct { T* p; }
plus all the constraints and casting operators you'd expect).From what I can tell, this is the key method that's needed to bridge the gap between pointers and generics. No need for adding many other pieces of support in the language, compiler, runtime, etc. Just let us temporarily reinterpret a
T*
as aPtr<T>
orIntPtr
to enable performing an operation that works fine on that type.In interop code, it's more idiomatic and less kludgey to just use pointers, and it's unsavory to have to switch everything to pointer-wrapping structs just because pointers aren't compatible with many things in the compiler or the framework. Once you have a field or variable that's a
T*
you are completely locked out of important operations that are idiomatic in native/interop code. You just can't break the pointer out of its jail without some really weird hacks that the JIT optimizer likely doesn't stand a chance against (someone found a crazy way using function pointers to accomplish this, but because of that it involves a non-inlinable method call).With this I can do
Interlocked
operations on pointers, I can doVolatile.Read()
andVolatile.Write()
on pointers, etc. I can reinterpret anIUnknown*
to aComPtr<IUnknown>
(from TerraFX). And as long as the inverse method is available, I can convert back to pointers when needed. Having to sandwich these withUnsafe
calls is also unsavory, but par for the course when working heavily with interop code andUnsafe
.With help from others (esp. @jakobbotsch) I was able to get a prototype of this and it does work. Not having this in the runtime is very inconvenient, but not completely blocking, as I could create a nuget package. However, having an assembly and nuget package for the sake of 1 method is a little heavy.
cc @Sergio0694 @jakobbotsch @tannergooding who were part of the discussion in Discord
API Proposal
The IL for this is pretty straightforward, it's just
ldarg.0
andret
, along with attributes to tagT
andU
as unmanaged. I did manage to get a working version of this with the help of @jakobbotschAPI Usage
Alternative Designs
No response
Risks
The naming of the method needs to be chosen very carefully.
If the non-pointer type being converted to/from is not at least pointer-sized, bad things can happen. But, this is
Unsafe
.The text was updated successfully, but these errors were encountered: