dotnet / dotnet/fsharp

"use fixed" construct does not unpin at end of scope

Open
#12,136 1 comment 0 reactions 0 assignees View on GitHub
Area-Compiler-CodeGen Feature Improvement
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 11h
Merged PRs (30d)
131

Description

If I pin an object with `use x = fixed expr` and then, after the end of the scope of `x`, do something else, the pinned object remains pinned until the function returns. E. g.
```fs
let used<'T>(t: 'T): unit = ignore t
let test(array: array): unit =
do
use pin = fixed &array.[0]
used pin
used 1
```
The variable `pin` remains pinned even during the call `used 1`, even through it should no longer exist.

This can be seen in the corresponding IL:
```cs
.method public static
void test (
int32[] 'array'
) cil managed
{
.maxstack 4
.locals init (
[0] native int 'pin',
[1] int32& pinned
)

// use temp = fixed &array.[0]
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: ldelema [System.Runtime]System.Int32
IL_0007: stloc.1
// let pin = temp
IL_0008: ldloc.1
IL_0009: conv.i
IL_000a: stloc.0
// used pin
IL_000b: ldloc.0
IL_000c: call void Main::used(!!0)
IL_0011: nop
// used 1
IL_0012: ldc.i4.1
IL_0013: call void Main::used(!!0)
IL_0018: nop
IL_0019: ret
}
```
Note that the variable with the `pinned` attribute does not get replaced with a null value, so it keeps pinning the array. This is in contrast with C#, where `ldc.i4.0 + conv.u + stloc.1` is added to unpin the array at the end of the scope of the fixed block, i. e. at `IL_0011`.

This also tends to break decompilers, which usually assume that the array will be unpinned. (This is especially bad if more than one object is pinned.) They tend to extend the C# fixed block as far as they can in the current block (e. g. until the end of an if block), which will cause *very short* fixed blocks if an array is pinned instead of an array element, because an array is first checked before being pinned. This can be seen in the (incorrect) decompilation result of DnSpy of the following code:
```fs
let test(array: array): unit =
do
use pin = fixed array
used pin
used 1
```
Decompiled:
```cs
public unsafe static void test(int[] array)
{
IntPtr intPtr;
if (array != null)
{
if (ArrayModule.Length(array) != 0)
{
fixed (int* ptr = &array[0])
{
intPtr = ptr;
}
}
else
{
intPtr = (IntPtr)0;
}
}
else
{
intPtr = (IntPtr)0;
}
IntPtr pinned = intPtr;
Main.used(pinned);
Main.used(1);
}
```

This also first led me to believe that the array only remains pinned during the assignment to the variable `pin`, but not during the call `used pin`, so I first thought that I couldn't use F# for low-level interop because F# didn't pin correctly. That's thankfully not the case.

**Known workarounds**

Put the block containing the fixed construct into another function, e. g.
```fs
let test(array: array): unit =
let doBlock() =
use pinned = fixed array
used pinned

doBlock()
used 1
```

See also #10832, where this caused confusion.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.