-
Notifications
You must be signed in to change notification settings - Fork 847
Description
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?