Skip to content
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

Unmanaged generic type constraint + generic pointers #4784

Closed
nietras opened this issue Dec 12, 2015 · 9 comments
Closed

Unmanaged generic type constraint + generic pointers #4784

nietras opened this issue Dec 12, 2015 · 9 comments
Labels
area-VM-coreclr enhancement Product code improvement that does NOT require public API changes/additions
Milestone

Comments

@nietras
Copy link
Contributor

nietras commented Dec 12, 2015

Add support for feature mentioned in http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4716089-unmanaged-generic-type-constraint-generic-pointe and example of usage at http://www.codingmurmur.com/2014/07/vote-for-unmanaged-generic-type.html

Some of the contents of the above links are repeated here for convenience.

E.g. add support for code such as:

unsafe void DoSomeStuff<T>() where T : unmanaged 
{ 
    T t = new T(); 
    T* tPointer = &t; 
    T* aBunchOfT = stackalloc T[100]; 
    var tSize = sizeof(T); 
    byte* aBunchOfBytes = stackalloc byte[tSize * 100]; 
    T* aBunchOfCastedT = (T*)bunchOfBytes; 
}

Getting support for an unmanaged generic type constraint and generic pointers combined with SIMD operations based on unsafe pointers would be absolutely awesome. If System.Numerics.Vectors had support for creating "vectors" over pointers, this would add to the benefit.

Currently, the lack of unmanaged generic type constraint and generic pointers means that, if you are doing image or graphics processing on different primitive types byte, sbyte, short, ushort, int, uint, long, ulong, float, double etc., then you have to repeat code for each of these - if you care about type safety and performance - leading to massive code bloat and combinatorial explosions where I have seen methods with thousands of overloads (using T4 code generation), which Visual Studios intellisense simply can't handle. Getting the above feature would allow a lot of this code to be boiled down to a single generic method e.g. (a draft example using value type trick to get inlining inside execution loop):

An example of this could be:

public interface IFunc<in T, out TResult>
{
    TResult Invoke(T arg);
}

public struct Threshold : IFunc<int, byte>
{
    readonly int m_threshold;

    public Threshold(int threshold)
    {
        m_threshold = threshold;
    }

    // Since this is a value type it will be inlined by JIT in release
    public byte Invoke(int value)
    {
        return value > m_threshold ? byte.MaxValue : byte.MinValue;
    }
}

public static class Transforms
{
    public unsafe static TFunc Transform<T, TResult, TFunc>(
        ArrayPtr2D<T> src, TFunc func, ArrayPtr2D<TResult> dst)
        where T : unmanaged
        where TFunc : struct, IFunc<T, TResult>
    {
        if (src.Size != dst.Size)
        { throw new ArgumentException("Arrays must have same size"); }

        var width = src.Size.Width;
        var height = src.Size.Height;

        var srcStride = src.StrideInBytes;
        T* srcRowPtr = src.DataPtr;
        var srcRowPtrEnd = ((byte*)srcRowPtr) + srcStride * height;

        var dstStride = dst.StrideInBytes;
        TResult* dstRowPtr = dst.DataPtr;

        for (; srcRowPtr != srcRowPtrEnd;
               srcRowPtr = (T*)(((byte*)srcRowPtr) + srcStride),
               dstRowPtr = (TResult*)(((byte*)dstRowPtr) + dstStride))
        {
            var srcColPtr = srcRowPtr;
            var srcColPtrEnd = srcColPtr + width;
            var dstColPtr = dstRowPtr;
            for (; srcColPtr != srcColPtrEnd; ++srcColPtr, ++dstColPtr)
            {
                *dstColPtr = func.Invoke(*srcColPtr);
            }
        }
        return func;
    }
}

public class Program
{
    public static void Main()
    {
        // Initialize from some existing native data i.e. from a bitmap etc.
        ArrayPtr2D<int> src = ...;
        ArrayPtr2D<byte> dst = ...;
        // Unsafe, fast, loop with threshold invoke inlined
        Transforms.Transform(src, new Threshold(1275), dst); 
    }
}

Keyword for this should, of course, be discussed in detail and may be different on different languages, so this is probably more an issue for Roslyn and not the CoreCLR/CoreRT as such.

Note how due to the transform having type 'T' as input and type 'TResult' as output we can handle a lot of combinations with this, without repeating the code. The JIT will, of course, have to generate specific code for each actual type usage etc. However, this is exactly what we want. Optimized code for each specific type combination and value type func. Who wouldn't want that ;)

F# already has the unmanaged generic type constraint as can be seen in Constraints (F# https://msdn.microsoft.com/en-us/library/dd233203.aspx), we just need this in C# and generic pointers to unmanaged types.

However, some have said this requires CLR support to be widespread usable across different languages, not sure this is true.

PS: It would additionally be great if the CoreCLR also had the IFunc<in T, outTResult> and similar interfaces mimicking delegate definitions for Action/Func (all variants) and with these delegates implementing these interfaces as this would allow direct usage of these together with the option of using the value type inline pattern. Of course, using a delegate could not necessarily be inlined but it would allow for convenience in using this pattern both for delegates and value types.

@yizhang82
Copy link
Contributor

@davidwrighton Do you know if there is any CLR type system work required in the VM for unmanaged T constraint? I don't think so, but I'd like to double check.

@terrajobst
Copy link
Contributor

Thanks for filing. We've talked about this today again. @jkotas and @jaredpar had some opinion on this as well.

@jamesqo
Copy link
Contributor

jamesqo commented Mar 12, 2016

Just curious, can't this be implemented as a library solution if dotnet/corefx#5474 is approved? Then you'd be able to write something like this independently of any compiler support:

public unsafe struct Pointer<T>
{
    private readonly void* pointer;

    public Pointer(void* pointer)
    {
        this.pointer = pointer;
    }

    public T Value
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get { return Unsafe.Read<T>(pointer); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        set { Unsafe.Write(pointer, value); }
    }
}

@nietras
Copy link
Contributor Author

nietras commented Mar 12, 2016

@jamesqo yes you can. And I have in fact done that, hence the open source project https://github.com/DotNetCross/Memory.Unsafe which I have used to make similar code and generic loops/transforms over unmanaged memory.

However, this has issues with performance since the code becomes more complex with more calls, which the JIT can't handle particularly well for value types, see https://github.com/dotnet/coreclr/issues/3539. The JIT does inline the calls but the resulting code still does a lot of unnecessary copies/moves.

Of course, if the JIT could handle this there would be less reason for an unmanaged (or primitive etc.) constraint and generic pointer support. Except, the code is a lot clearer using this, less complex, easier to JIT etc. So I still think the proposal is relevant.

@jamesqo
Copy link
Contributor

jamesqo commented Mar 13, 2016

@nietras Interesting findings, I hadn't realized the data would still have to be copied after inlining. What if we made the pointer APIs in question specially recognized by the JIT, so it could elide those copies?

@jkotas
Copy link
Member

jkotas commented Mar 13, 2016

The JIT should be able to elide those copies without special recognizing the pointer APIs.

@nietras
Copy link
Contributor Author

nietras commented Mar 15, 2016

The JIT should be able to elide those copies without special recognizing the pointer APIs.

Yes, that would be ideal. Especially, since in the example code given we use the value type IFunc trick to get these inlined where value types could be parameters or returned from the func, and for these cases we would also like copies to be elided.

@4creators
Copy link
Contributor

@jkotas @tannergooding

Seems that the proposal is already implemented and remaining issue are tracked in csharplang repo. IMO this should be closed.

@jkotas
Copy link
Member

jkotas commented Sep 23, 2018

Agree

@jkotas jkotas closed this as completed Sep 23, 2018
@msftgits msftgits transferred this issue from dotnet/coreclr Jan 30, 2020
@msftgits msftgits added this to the Future milestone Jan 30, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Jan 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-VM-coreclr enhancement Product code improvement that does NOT require public API changes/additions
Projects
None yet
Development

No branches or pull requests

7 participants