Roslyn: [BUG?] Bypassing "cannot take the pointer of a local variable shared by anonymous functions."

Created on 12 Jun 2020  路  7Comments  路  Source: dotnet/roslyn

Description


There is a limitation in C# that ensures that a local variable cannot be changed unintentionally which is shared by anonymous functions (Func<,,,>). What the limitation does is blocking getting the address of that local variable.

image

However, I was able to bypass that limitation with a "ref" keyword.

        public unsafe static (Func<int>, Func<int>, IntPtr) A()
        {
            int x = 5;

            //int* ptr = &x; //throws error

            return 
            (() => 
            {
                x--;
                return x; 
            }, 

            () =>
            {
                x+=10;
                return x;
            }, 

            (IntPtr)B(ref x));
        }

        public unsafe static int* B(ref int p)
        {
            fixed(int* ptr = &p)
                return ptr;
        }

        unsafe static void Main(string[] args)
        {
            var (f1, f2, p) = A();
            int* ptr = (int*)p;

            Console.WriteLine(f1());
            Console.WriteLine(f2());
            *ptr = -10;
            Console.WriteLine(f1());
            Console.WriteLine(f2());
        }

There are already other solutions probably to bypassing the limitation.
However, being not able to get the pointer of a shared local variable and being able to pass the variable with a ref keyword at the same time is not logical. I'd like to suggest block the ref keyword usage with the local variables shared by anonymous functions or let both available to the developers.

Configuration

Which version of .NET is the code running on? .NET5 preview x

Output of the example code

image

Area-Compilers

Most helpful comment

You could consider using ComputeSharp to make using your GPU a lot easier - https://github.com/Sergio0694/ComputeSharp - absolutely awesome project

All 7 comments

There is a limitation in C# that ensures that a local variable cannot be changed unintentionally which is shared by anonymous functions (Func<,,,>)

I don't think this is a semantic feature designed to stop mutation of locals, given that would already happen.

It is logical that an arbitrary captured variable cannot be directly used in an addressof expression, as it is not actually a local, and is hoisted into a display class which isn't guaranteed to be pinned. However, the compiler does not let the variable be directly in a fixed addressof block, which doesn't seem the desired behaviour. It will allow this when a temporary ref is taken, and then that is pinned, rather than the variable itself

```C#
private static unsafe void Main(string[] args)
{
int x = 1;

fixed (int* z = &x) // error
{
    Task.Run(() => Foo(x));
}

}

public static unsafe void Foo(int z) { }

```C#
private static unsafe void Main(string[] args)
{
    int x = 1;

    ref int k = ref x;

    fixed (int* z = &k) // legal
    {
        Task.Run(() => Foo(x));
    }
}

public static unsafe void Foo(int z) { }

Also, not relevant to the issue, but your code isn't semantically correct, if this is actual real code. You are trying to return a pointer to memory that isn't guaranteed to exist. Returning a pointer from within a fixed block, or returning a pointer to a local, is undefined behaviour

Yes, I understand the usage of fixed and the semantics. So what this is all about is a lifetime of a variable, right? Since "int x" is not a global variable, it might be removed from memory in the future(1). By using ref, we pin the original variable, then we can take the pointer which is meaningful, it's guaranteed to exist. Otherwise, because the variable is not pinned, we can't directly access the pointer.

1) One more thing, why is the variable not guaranteed to be pinned?
Does C# can understand if the variable is still in the field, or to be used? Does the lifetime of the shared variable depend on whether the dynamic functions that use the variable still exist? Is C# that deep to check on dynamically created functions?

//I'm sorry to bring this issue to you, it was time-wasting. Thank you for explaining it though.

Since "int x" is not a global variable

It is secretly a global variable. Because it is captured by a lambda, the compiler hoists it to one.

One more thing, why is the variable not guaranteed to be pinned?

Because a variable is only guaranteed to be pinned inside a fixed block. You return it from the fixed block, which means it is no longer fixed and can move or be deleted

//I'm sorry to bring this issue to you, it was time-wasting. Thank you for explaining it though.

No problem 馃槃 , and I think you found a small bug anyway so it wasn't a waste of time

@john-h-k I love GitHub just because of this, I can meet new people and look up what's in their repositories 馃槃 . I see you are doing low-level optimization and some GPU stuff.

I was also developing a deep learning library in C# as well (low-level CPU optimizations etc and GPU). Before adding GPU support I gave up from the idea which is creating a deep learning library in C#. Because I am solo developing it's been hard, really really hard. To design the library, to prepare all the CPU and GPU kernels, to decide how the network will flow like one kernel at a time? or multiple kernels running on multiple GPUs?

here is the link if you want to take a look: https://github.com/faruknane/DeepLearningFramework

You could consider using ComputeSharp to make using your GPU a lot easier - https://github.com/Sergio0694/ComputeSharp - absolutely awesome project

The behavior of the compiler here is "By Design". The compiler doesn't attempt to track the variables across method bodies in the manner that is being suggested here.

Was this page helpful?
0 / 5 - 0 ratings