Skip to content

Volatile and atomic operations on unmanaged memory (native pointer dereference, strong type checking) #409

@zpodlovics

Description

@zpodlovics

I would like to be able to do volatile and atomic operations on unmanaged memory from .NET in F#. However it seems that there is no way to express the same operations in F# due missing pieces.

The original issue comes from:
https://github.com/dotnet/coreclr/issues/916

Based on the provided example I created sample program in C# which looks working:
https://gist.github.com/zpodlovics/6f4195441f88f01cd0b3

I created a helper method in C# to figure out how the void* type is represented in F# which looks like nativeptr in F# and int32* type looks like nativeptr in F#.

unsafe public static void* ToVoidPtr(IntPtr value) {
  return (void*)value;
}
unsafe public static int* ToInt32Ptr(IntPtr value) {
  return (int*)value;
}
var ptr = Marshal.AllocHGlobal (sizeof(int));
Marshal.WriteInt32 (ptr, 0);
System.Console.WriteLine ("Ptr Old Value (Marshal): {0}", Marshal.ReadInt32(ptr));
var pOld = Interlocked.CompareExchange (ref *(Int32*)ptr, 1, 0);
System.Console.WriteLine ("Ptr Old Value (CompareExchange): {0}", pOld);
System.Console.WriteLine ("Ptr New Value (Marshal): {0}", Marshal.ReadInt32(ptr))

The CompareExchange call IL looks like this:

    IL_0024:  ldloc.0
    IL_0025:  call void* native int::op_Explicit(native int)
    IL_002a:  ldc.i4.1
    IL_002b:  ldc.i4.0
    IL_002c:  call int32 class [mscorlib]System.Threading.Interlocked::CompareExchange([out] int32&, int32, int32)
    IL_0031:  stloc.1

Typed ptr version:

var ptr = Marshal.AllocHGlobal (sizeof(int));
Marshal.WriteInt32 (ptr, 0);
var intPtr = ToInt32Ptr (ptr);
System.Console.WriteLine ("Ptr Old Value (Marshal): {0}", Marshal.ReadInt32(ptr));
var pOld = Interlocked.CompareExchange (ref *intPtr, 1, 0);
System.Console.WriteLine ("Ptr Old Value (CompareExchange): {0}", pOld);
System.Console.WriteLine ("Ptr New Value (Marshal): {0}", Marshal.ReadInt32(ptr));

The CompareExchange call IL looks like this:

    IL_0010:  call int32* class UnmanagedMemory.MainClass::ToInt32Ptr(native int)
    IL_0015:  stloc.1 
    IL_0016:  ldloc.1 
    IL_0017:  ldc.i4.1 
    IL_0018:  ldc.i4.0 
    IL_0019:  call int32 class [mscorlib]System.Threading.Interlocked::CompareExchange([out] int32&, int32, int32)
    IL_001e:  stloc.2 

F# implementation (without the pointer dereference)

    let ptr = Marshal.AllocHGlobal(sizeof<int32>) in   
    Marshal.WriteInt32(ptr, 0);
    System.Console.WriteLine("Old Value: {0}", Marshal.ReadInt32(ptr));
    //C# helper
    //let mutable intPtr = UnmanagedMemoryHelper.MyClass.ToVoidPtr(ptr) in
    let mutable intPtr = NativePtr.ofNativeInt<unit> ptr
    let pOld = Interlocked.CompareExchange(&intPtr, 1, 0);

I also tried with nativeptr<int32>

    let ptr = Marshal.AllocHGlobal(sizeof<int32>) in   
    Marshal.WriteInt32(ptr, 0);
    System.Console.WriteLine("Old Value: {0}", Marshal.ReadInt32(ptr));
    //C# helper
    //let mutable intPtr = UnmanagedMemoryHelper.MyClass.ToInt32Ptr(ptr) in
    let mutable intPtr = NativePtr.ofNativeInt<int32> ptr
    let pOld = Interlocked.CompareExchange(&intPtr, 1, 0);

The closest thing I found in the documentation for dereference is the NativePtr.read but this will copy the value from the ptr to a new variable.

https://msdn.microsoft.com/en-us/library/ee353827.aspx

    [<NoDynamicInvocation>]
    [<CompiledName("ReadPointerInlined")>]
    let inline read (p : nativeptr<'T>) = (# "ldobj !0" type ('T) p : 'T #) 
    let ptr = Marshal.AllocHGlobal(sizeof<int32>) in   
    Marshal.WriteInt32(ptr, 0);
    let mutable intPtr = UnmanagedMemoryHelper.MyClass.ToInt32Ptr(ptr);
    System.Console.WriteLine("Old Value: {0}", Marshal.ReadInt32(ptr));
    let mutable intVal = (NativePtr.read intPtr)
    let pOld = Interlocked.CompareExchange(&intVal, 1, 0);
    System.Console.WriteLine("New Value: {0}", Marshal.ReadInt32(ptr));

The output will be:

Old Value: 0
New Value: 0

Instead of:

Old Value: 0
New Value: 1

Maybe I miss something but it seems that I cannot express the same operations in F#. Is there any way to express a pointer dereference in F#? Is there any way to express the same operations in F# that is available in the C# sample?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions