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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions