Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type nativeptr<unit> isn't usable at all #4166

Closed
realvictorprm opened this issue Dec 24, 2017 · 31 comments
Closed

Type nativeptr<unit> isn't usable at all #4166

realvictorprm opened this issue Dec 24, 2017 · 31 comments
Milestone

Comments

@realvictorprm
Copy link
Contributor

realvictorprm commented Dec 24, 2017

Framework code which uses the type nativeptr<unit> isn't usable in F# because we cannot cast any nativeptr to nativeptr<unit>. Exact error is that unit isn't an unmanaged type.

I noticed this after trying to compile simple examples with the Span type from the System.Memory package. Because the C# equivalent of nativeptr<unit> is void* and nothing is wrong with a void pointer I thought reporting this here would be correct.

To have a senseful usage example look at this code (requires the System.Memory package!):

let someOperationWithSpan () =
    let data = NativePtr.stackalloc<byte>(256)
    let voidPtr = data |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<unit> // This line doesn't compile
    let stackMemory = new Span<byte>(voidPtr, 256)
    // Do some stuff with the span!
    ()
@realvictorprm
Copy link
Contributor Author

Sorry I posted the wrong conversion code 😄
Corrected it to the right example.

I see some problems with allowing unit to be an unmanaged type in general. I would suggest to add a function to FSharp.Core which allows a conversion from any nativeptr kind to nativeptr<unit>.

@realvictorprm
Copy link
Contributor Author

Already created a Hacky fix for this in the compiler. It just changes in the type checker that the unit type passes the unmanaged constrain and the initial sample with stackalloc using unit works without runtime error.

I assume that size of the type should be calculated as 1 byte?

@saul
Copy link
Contributor

saul commented Dec 24, 2017

What are you trying to do? Casting to unit doesn’t really make any sense.

@realvictorprm
Copy link
Contributor Author

@saul the constructor requires an void pointer :)

I'm going to correct the example later, having Christmas right now.

@realvictorprm
Copy link
Contributor Author

@saul I updated the example to be correct.

realvictorprm added a commit to realvictorprm/visualfsharp that referenced this issue Dec 24, 2017
Signed-off-by: realvictorprm <[email protected]>
@poizan42
Copy link

poizan42 commented Dec 25, 2017

Note that a workaround is to use IntPtr.ToPointer:

let someOperationWithSpan () =
    let data = NativePtr.stackalloc<byte>(256)
    let voidPtr = (data |> NativePtr.toNativeInt).ToPointer()
    let stackMemory = new Span<byte>(voidPtr, 256)
    // Do some stuff with the span!
    ()

Now the claimed inferred type of voidPtr is nativeptr<unit>, but attempting to explicitly declare it as such results in the same error message. Overloaded meaning of types strikes again!

There is also IntPtr.op_Explicit for IntPtr -> void*, but there is no way of selecting the correct overload in F#.

@realvictorprm
Copy link
Contributor Author

@poizan42 after I applied a fix in the compiler I ran into the same issue. This then is a bug I guess?

@poizan42
Copy link

@realvictorprm, yeah - there are actually several problems in F# like this happening because the same F# type can mean different .NET types. I think there might be more to fixing this than just marking unit as unmanaged, because unit can refer to both Microsoft.FSharp.Core.Unit and System.Void.

@realvictorprm
Copy link
Contributor Author

Must be a bug.
cc @dsyme

@zpodlovics
Copy link

I am afraid it may need deeper changes in the system, than a few lines of change in the code. Without supporting the core low level stuff eg.: Reference semantics with value types constructs (all of them) F# may not be able to use the new constructs in the base libraries. The current unit type aliasing and the byref constructs are far from perfect in F#. I had to move for sevaral performance critical code to C# , because F# cannot express it properly for example structs types with indexed O(1) self element access.:

#843
#3525

"Another related language feature is the ability to declare a value type that must be stack allocated. In other words, these types can never be created on the heap as a member of another class. The primary motivation for this feature was Span and related structures. Span may contain a managed pointer as one of its members, the other being the length of the span. It's actually implemented a bit differently because C# doesn't support pointers to managed memory outside of an unsafe context. Any write that changes the pointer and the length is not atomic. That means a Span would be subject to out of range errors or other type safety violations were it not constrained to a single stack frame. In addition, putting a managed pointer on the GC heap typically crashes at JIT time."

https://docs.microsoft.com/en-us/dotnet/csharp/reference-semantics-with-value-types

@realvictorprm
Copy link
Contributor Author

Hm, I don't like this.

@zpodlovics did you ever open a language suggestion etc. ?

@zpodlovics
Copy link

zpodlovics commented Dec 30, 2017

@realvictorprm Thanks for your help! I don't like it either. I would like to keep all the development in F# as much as possible, because I could retarget the high level parts later to different runtime eg.: fable, fez, fshlvm, etc.

I ran into some corner cases and issues in the past with F# that was related with these low level stuff (unmanaged atomics, byref restrictions, struct units of measure types, struct du, , etc). Most of the issues was fixed, but some of them was remained unaddressed (which is not unreasonable, as the netstandard and sdk integration was lot more important).

Please do not take this as offense, I would like to use Span as much as you, in fact I already tried to use it in development (with some C# helpers, because I am not yet figured out how to do it fully or partially in F# or inline IL eg.: by loading load a void* type to F# with custom typeofvoidptr function - something like tobyref for loading void* types #409). However reading the original design span design docs and ref like design docs (the .net core design reviews are also avaialble for span at https://www.youtube.com/playlist?list=PLRAdsfhKI4OWIV9TuZ0QmmlQzz41o80j0) I realized the missing parts are may larger than I earlier estimated (eg.: stack only types, readonly byref types, byref restrictions), and using it properly - especially in production - may require lot more. In production. I had to satisfy different constraints, and running into weird unsolvable corner cases is not fun. I would love to make a technically sound language change plans, but I am afraid I am not there yet.

Here is the decompiled C# helper. Unfortunately I was not able to call the op_explicit cast without expressing the void* type somehow.

    // method line 2
    .method public static hidebysig 
           default void* ToVoidPtr (native int 'value')  cil managed 
    {
        // Method begins at RVA 0x206c
	// Code size 12 (0xc)
	.maxstack 1
	.locals init (
		void*	V_0)
	IL_0000:  nop 
	IL_0001:  ldarg.0 
	IL_0002:  call void* native int::op_Explicit(native int)
	IL_0007:  stloc.0 
	IL_0008:  br.s IL_000a

	IL_000a:  ldloc.0 
	IL_000b:  ret 
    } // end of method VoidPtr::ToVoidPtr

Span design docs:

https://github.com/dotnet/corefxlab/blob/master/docs/specs/span.md
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md
https://youtube.com/watch?v=VKnHYFIQpOs
https://youtube.com/watch?v=jKQWMCPZ08Y
https://youtube.com/watch?v=10w3tS03W60

@realvictorprm
Copy link
Contributor Author

@poizan42 I noticed that my fix also applies for the overload resolution. Now I try to find some examples for testing whether the feature works as expected.

@realvictorprm
Copy link
Contributor Author

The next issue I ran into with Span is the Index access. It has the return type byref<'T> which is not really usable right now. E.g. any call to printf fails because byref can't be used for the function.

@cartermp
Copy link
Contributor

@realvictorprm Is this a subset of #4288?

@realvictorprm
Copy link
Contributor Author

realvictorprm commented Feb 14, 2018 via email

@zpodlovics
Copy link

zpodlovics commented Mar 2, 2018

It seems that the same code (with the .ToPointer() trick) no longer works with the .NET 2.1 Preview1 using <TargetFramework>netcoreapp2.1</TargetFramework> in the fsproj.

open System
open Microsoft.FSharp.NativeInterop

[<EntryPoint>]
let main argv =
    let data = NativePtr.stackalloc<byte>(256)
    let voidPtr = (data |> NativePtr.toNativeInt).ToPointer()
    let stackMemory = new Span<byte>(voidPtr, 256)
    printfn "Hello World from F#!"
    0 // return an integer exit code
error FS0101: This construct is deprecated. Types with embedded references are not supported in this version of your compiler.

Environment: Ubuntu 16.04 x86_64
Packages:

ii  dotnet-host                                                 2.1.0-preview1-26216-03-1                                   amd64        Microsoft .NET Core Host - 2.1.0 Preview 1
ii  dotnet-hostfxr-2.1.0-preview1-26216-03                      2.1.0-preview1-26216-03-1                                   amd64        Microsoft .NET Core Host FX Resolver - 2.1.0 Preview 1 2.1.0-preview1-26216-03
ii  dotnet-runtime-2.1.0-preview1-26216-03                      2.1.0-preview1-26216-03-1                                   amd64        Microsoft .NET Core Runtime - 2.1.0 Preview 1 Microsoft.NETCore.App 2.1.0-preview1-26216-03
ii  dotnet-runtime-deps-2.1.0-preview1-26216-03                 2.1.0-preview1-26216-03-1                                   amd64        dotnet-runtime-deps-2.1.0-preview1-26216-03 2.1.0-preview1-26216-03
ii  dotnet-sdk-2.1.300-preview1-008174                          2.1.300-preview1-008174-1                                   amd64        Microsoft .NET Core SDK 2.1.300 - Preview

Related issue:
https://github.com/dotnet/corefx/issues/25669

@dsyme
Copy link
Contributor

dsyme commented Mar 2, 2018

error FS0101: This construct is deprecated. Types with embedded references are not supported in this version of your compiler.

I don't know where that error is coming from, it's not an F# error

@dsyme
Copy link
Contributor

dsyme commented Mar 2, 2018

Oh, it's a deprecation error....

  | ObsoleteError _ -> 101

@svick
Copy link
Contributor

svick commented Mar 2, 2018

@zpodlovics @dsyme All ref structs (or "ref-like structs"), including Span<T>, are marked with an [Obsolete] attribute and that message, to make sure a compiler that doesn't support them won't be able to use them.

See the proposal and this constant in Roslyn.

@dsyme
Copy link
Contributor

dsyme commented Mar 2, 2018

OK, I can see that raises the priority of Span work...

@dsyme
Copy link
Contributor

dsyme commented Mar 2, 2018

@zpodlovics language suggestion at fsharp/fslang-suggestions#648

@VSadov
Copy link
Member

VSadov commented Mar 3, 2018

Note that while escape rules for ref structs in C# are rather complex, most of that comes from supporting wrapping stackalloc buffers in spans.

C# supports things like:

Method()
{
       // obviously this span or its slices cannot be allowed to be returned from the containing method
       Span<int> sp = stackalloc int[10];
}

This is a fairly advanced scenario.

In reality most of the ref-like structs are either transient to your code or wrap heap data (strings, arrays, etc..). In those cases escaping is not a concern.

I'd recommend to not care about stackalloc at first, or possibly ever. Then you only need to guarantee the "no heap" part, which is simpler. - may not be boxed, may not be a generic type argument, may not be a field unless an instance field of another ref struct, and so on.

We had most of these rules already implemented for old "unboxable" types such as TypedReference. The only difference is that there are couple scenarios that are allowed for ref-struct, but for whatever reasons were not allowed for TypedReference.

@VSadov
Copy link
Member

VSadov commented Mar 3, 2018

And, yes, ReadOnlySpan has an indexer that is ref readonly. That will drag in the readonly references.

Good explanation is here:
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md

Those features are useful on their own.

Conceptually they just allow to specify that a byref is readonly.

That could be used, for example, for passing around direct references to a large struct field or an array element just for reading while avoiding unnecessary copying.

@zpodlovics
Copy link

@VSadov We already have similar code snippets in F# due the latency / GC / performance requirements, but with NativePtr.stackalloc / NativePtr.read / NativePtr.write / NativePtr.get / NativePtr.set. [1] [2] [3]

Please do NOT drop stackalloc support in F# and especially for Span just because it would be easier to implement initially (but at the same time the some thing stackalloc/Span will be possible in C#), because doing latency / gc / performance critical code would be imppossible in F# otherwise. The F# language will become medocrie tool for latency / gc / performance critical code compared to C#. F# should never be a second class citizen in .NET or other runtime for latency / gc / performance critical code.

I intentionally used the Easy instead of Simple here. Using stackalloc and Span should be Simple in F#, but not necessary Easy to learn and do: https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/SimpleMadeEasy.md

[1] #843
[2] #3525
[3] #409

@zpodlovics
Copy link

Fun fact, it's not yet not possible (or I found a way) to cast a void* (F#: nativeptr<unit>) to int* (fsharp: nativeint) in F#. The Unsafe package is coming by default with AspNet Core. Probably because of for safety reasons(?) the F# unit type defined as a class type in https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs#L35

I tried the Unsafe package because it's not yet possible to express a byref<'T> -> nativeptr<'T> cast in F# when used it within a method (eg.: this.PassByRef<'T when 'T: unmanaged>(r: 'T byref)). AddressOf operator fails with "The byref type calue cannot be used at this point" the fixed operator fails with "Invaid use of fixed" because it's limited to address of a field address of an array element or string.

The following example will not compile, because it will result a compilation error:

Copyright (C) Microsoft Corporation. All rights reserved.

  Restoring packages for /tmp/nativeptrunitunmanaged/nativeptrunitunmanaged.fsproj...
  Generating MSBuild file /tmp/nativeptrunitunmanaged/obj/nativeptrunitunmanaged.fsproj.nuget.g.props.
  Restore completed in 263.02 ms for /tmp/nativeptrunitunmanaged/nativeptrunitunmanaged.fsproj.
/tmp/nativeptrunitunmanaged/Program.fs(22,24): error FS0001: A generic construct requires that the type 'unit' is an unmanaged type [/tmp/nativeptrunitunmanaged/nativeptrunitunmanaged.fsproj]

Build FAILED.

/tmp/nativeptrunitunmanaged/Program.fs(22,24): error FS0001: A generic construct requires that the type 'unit' is an unmanaged type [/tmp/nativeptrunitunmanaged/nativeptrunitunmanaged.fsproj]
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:00:04.53

nativeptrunitunmanaged.fs

module Test

open System
open System.Runtime.InteropServices
open System.Runtime.CompilerServices
open Microsoft.FSharp.NativeInterop

#nowarn "9"

[<Struct>]
type S =
    val X: int
    new(x)={X=x}    

let toPtr (x: 'T byref) =
    Unsafe.AsPointer<'T>(&x)

[<EntryPoint>]
let main argv =
    let mutable s = S(1)
    let vPtr = toPtr &s
    let nPtr = vPtr |> NativePtr.toNativeInt

    printfn "no nativeptr<unit> casting in F#"
    0 // return an integer exit code

nativeptrunitunmanaged.fsproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <EnableDefaultCompileItems>false</EnableDefaultCompileItems>    
    <RuntimeFrameworkVersion>2.0.5</RuntimeFrameworkVersion>                
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
    <PackageReference Include="FSharp.Core" Version="4.3.*" />
    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0" />
  </ItemGroup>
</Project>
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace S
{
    struct S
    {
        public int V0;
        public S(int v0) {
            V0=v0;
        }
    }

    class MainClass
    {
        public unsafe static void* AsPointer<T>(ref T value)
        {
            return Unsafe.AsPointer(ref value);
        }

        unsafe public static void Main (string[] args)
        {
            S s = new S(1);
            void* vPtr = AsPointer(ref s);
            int* iPtr = (int*)vPtr;

            Console.WriteLine ("Hello World!");
        }
    }
}

@ghost
Copy link

ghost commented May 12, 2018

@zpodlovics i dont want to muddle up your issues with irrelevant stuff, but I was wondering if the following escape hatch works for your situation:

let inline cast<'T when 'T : unmanaged>(p : byref<'T>) : nativeptr<'T> = (# "" p : nativeptr<'T> #);;

@zpodlovics
Copy link

@abk-x Nope, already tried, also with type aliases, but you still cannot load the void* type. However thanks to @dsyme excellent work this will soon solved as voidptr type [1] and the related functions NativePtr in module.

[1] https://github.com/Microsoft/visualfsharp/pull/4888/files#diff-085a94dd8e50b6ab66ae9a3263edcdabR70
[2] https://github.com/Microsoft/visualfsharp/pull/4888/files#diff-bd687a90f772d196d48168173f66a023R28

@ghost
Copy link

ghost commented May 12, 2018

Was going to suggest a pinvoke escape hatch, which I believe would work, but I think a language level thing is much more appropriate, and testable !

@dsyme
Copy link
Contributor

dsyme commented May 14, 2018

The proposal is to address this by adding voidptr in this RFC: https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1053-span.md

Please take a look and review carefully, providing evil test cases where possible.

@cartermp cartermp added this to the 15.8 milestone Aug 29, 2018
@cartermp
Copy link
Contributor

This code is now possible with F# 4.5:

open Microsoft.FSharp.NativeInterop

let someOperationWithSpan () =
    let data = NativePtr.stackalloc<byte>(256)
    let voidPtr = data |> NativePtr.toVoidPtr
    let stackMemory = new Span<byte>(voidPtr, 256)
    // Do some stuff with the span!
    ()

Closing, since voidptr is now a concept in the language.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants