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

Native interop: not possible to get/use a pointer of a managed struct #843

Closed
zpodlovics opened this issue Dec 30, 2015 · 9 comments
Closed

Comments

@zpodlovics
Copy link

I would like to have direct (pointer) access to the managed structs, in order to provide array like access to the structs internals (both the fields, both the whole struct internal memory area - the safe access is my responsibility), however it seems not possible to do it from F#. Every struct type is unmanaged (blittable), so it has the same native representation as the managed representation. The heap allocation unfortunately is not an option, due the excessive amount of generated data.
AddressOf operator (&&) not working on structs, and I also tried a similar trick (nativeptr<'T> -> byref<'T>) that worked on #409 but on the reverse (byref<'T> -> nativeint, byref<'T> -> nativeptr<'T>) order. A similar example works fine in C#, but the C# IL seems to use a different opcode: ldind.i4

open System

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

#nowarn "9"
#nowarn "42"

module NativePtrExtension = begin
    [<NoDynamicInvocation>]
    let inline toNativeInt (x: 'T byref) = (# "" x : nativeint #)

end

[<NoComparison;NoEquality>]
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type Vector =
    val mutable V0: int
    val mutable V1: int
    val mutable V2: int
    val mutable V3: int
    val mutable V4: int
    val mutable V5: int
    val mutable V6: int
    val mutable V7: int
    new(v0,v1,v2,v3,v4,v5,v6,v7) = { V0=v0; V1=v1; V2=v2; V3=v3; V4=v4; V5=v5; V6=v6; V7=v7 }

module Main = begin
  open NativePtrExtension

  let read (x: nativeptr<Vector>) =
    let ptr = x |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<int>
    NativePtr.read ptr

  let get (x: nativeptr<Vector>) i =
    let ptr = x |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<int>
    NativePtr.get ptr i

  [<EntryPoint>]
  let main argv =
    let mutable s = Vector(0,1,2,3,4,5,6,7)

    //this will not compile: error FS0421: The address of the variable 's' cannot be used at this point
    //let addr = &&s

    let ptr = NativePtrExtension.toNativeInt &s

    if nativeint(0) <> ptr then
      System.Console.WriteLine("ptr={0}", ptr)
      let tptr = ptr |> NativePtr.ofNativeInt<Vector>
      let v0 = read tptr
      System.Console.WriteLine(v0)
      let v1 = get tptr 1
      System.Console.WriteLine(v1)
      let v2 = get tptr 2
      System.Console.WriteLine(v2)
    else
      System.Console.WriteLine("ptr=0x0")

    0
end

The same NativePtr operations works just fine when the ptr is created with localloc opcode, which is also stack allocated (but will be destroyed on fn exit) as the managed structs.

let ptr = NativePtr.stackalloc<Vector> 1
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace CSharpStruct
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct Vector
    {
        public int V0;
        public int V1;
        public int V2;
        public int V3;
        public int V4;
        public int V5;
        public int V6;
        public int V7;
        public Vector(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) {
            V0=v0;
            V1=v1;
            V2=v2;
            V3=v3;
            V4=v4;
            V5=v5;
            V6=v6;
            V7=v7;
        }

        unsafe public static int Get(Vector* vector,int i)   // Indexer declaration
        {

            int* ptr =&(vector->V0);
            return *(ptr + i);
        }           
    }

    class MainClass
    {
        unsafe public static void Main (string[] args)
        {
            Vector v = new Vector(0,1,2,3,4,5,6,7);
            Vector* ptr = &v;

            int v1 = Vector.Get (ptr, 1);
            System.Console.WriteLine (v1);
            int v2 = Vector.Get (ptr, 2);
            System.Console.WriteLine (v2);

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

C# Main (partial) IL looks just like the original code:

    .method public static hidebysig 
           default void Main (string[] args)  cil managed 
    {
        // Method begins at RVA 0x20b4
    .entrypoint
    // Code size 57 (0x39)
    .maxstack 9
    .locals init (
        valuetype CSharpStruct.Vector   V_0,
        valuetype CSharpStruct.Vector*  V_1,
        int32   V_2,
        int32   V_3)
    IL_0000:  ldloca.s 0
    IL_0002:  ldc.i4.0 
    IL_0003:  ldc.i4.1 
    IL_0004:  ldc.i4.2 
    IL_0005:  ldc.i4.3 
    IL_0006:  ldc.i4.4 
    IL_0007:  ldc.i4.5 
    IL_0008:  ldc.i4.6 
    IL_0009:  ldc.i4.7 
    IL_000a:  call instance void valuetype CSharpStruct.Vector::'.ctor'(int32, int32, int32, int32, int32, int32, int32, int32)
    IL_000f:  ldloca.s 0
    IL_0011:  stloc.1 

F Main (partial) IL looks a bit weird:

    .method public static 
           default int32 main (string[] argv)  cil managed 
    {
        .custom instance void class [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::'.ctor'() =  (01 00 00 00 ) // ....

        // Method begins at RVA 0x20b8
    .entrypoint
    // Code size 104 (0x68)
    .maxstack 10
    .locals init (
        valuetype Structptr/Vector  V_0,
        native int  V_1,
        valuetype Structptr/Vector& V_2,
        native int  V_3,
        int32   V_4,
        int32   V_5)
    IL_0000:  ldc.i4.0 
    IL_0001:  ldc.i4.1 
    IL_0002:  ldc.i4.2 
    IL_0003:  ldc.i4.3 
    IL_0004:  ldc.i4.4 
    IL_0005:  ldc.i4.5 
    IL_0006:  ldc.i4.6 
    IL_0007:  ldc.i4.7 
    IL_0008:  newobj instance void valuetype Structptr/Vector::'.ctor'(int32, int32, int32, int32, int32, int32, int32, int32)
    IL_000d:  stloc.0 
    IL_000e:  ldloca.s 0
    IL_0010:  stloc.2 
    IL_0011:  ldloc.2 
    IL_0012:  ldobj Structptr/Vector
    IL_0017:  stloc.1 
    IL_0018:  ldc.i4.0 
    IL_0019:  conv.i 
    IL_001a:  ldloc.1 

C# indexed access IL:

    .method public static hidebysig 
           default int32 Get (valuetype CSharpStruct.Vector* 'vector', int32 i)  cil managed 
    {
        // Method begins at RVA 0x2090
    // Code size 14 (0xe)
    .maxstack 3
    .locals init (
        int32*  V_0)
    IL_0000:  ldarg.0 
    IL_0001:  ldflda int32 CSharpStruct.Vector::V0
    IL_0006:  stloc.0 
    IL_0007:  ldloc.0 
    IL_0008:  ldarg.1 
    IL_0009:  ldc.i4.4 
    IL_000a:  mul 
    IL_000b:  add 
    IL_000c:  ldind.i4 
    IL_000d:  ret 
    } // end of method Vector::Get

F# Indexed access IL:

    .method public static 
           default int32 get (valuetype Structptr/Vector* x, int32 i)  cil managed 
    {
        .custom instance void class [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::'.ctor'(int32[]) =  (
        01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) // ................

        // Method begins at RVA 0x2098
    // Code size 19 (0x13)
    .maxstack 5
    .locals init (
        native int  V_0)
    IL_0000:  ldarg.0 
    IL_0001:  stloc.0 
    IL_0002:  ldloc.0 
    IL_0003:  ldarg.1 
    IL_0004:  conv.i 
    IL_0005:  sizeof [mscorlib]System.Int32
    IL_000b:  mul 
    IL_000c:  add 
    IL_000d:  ldobj [mscorlib]System.Int32
    IL_0012:  ret 
    } // end of method Main::get

According to the documentation [1]:
"The ldind.i4 instruction indirectly loads an int32 value from the specified address (of type native int, &, or *) onto the stack as an int32.

All of the ldind instructions are shortcuts for a Ldobj instruction that specifies the corresponding built-in value class."

I know that the match based field access could work, but it generate ineffective code (giant switch with lot's of branches which will be compiled to native cmp/jmp pairs) compared to a simple address based calculation and load.

How to drive F# Adoption PartN:
Please provide at least the same interoperability features as C# provides.
Please provide full opcode (it seems the IL writer not support every opcode due the missing parser and/or pickler support) and full documentation for inline IL, so it would be easier to add new IL features later, similarly how the initblk, cpblk added to NativePtr.

[1] https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldind_i4%28v=vs.110%29.aspx

@zpodlovics
Copy link
Author

More or less (C# 7) related issues:

dotnet/roslyn#118
dotnet/roslyn#126

@zpodlovics
Copy link
Author

According to the TastOps comments it's possible to get the address of a struct if the value is immutable (+some condition).

// We can take the address of values of struct type even if the value is immutable
// under certain conditions
//   - all instances of the type are  known to be immutable; OR
//   - the operation is known not to mutate

https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/TastOps.fs#L5340

    // We can take the address of values of struct type if the operation doesn't mutate 
    // and the value is a true local or closure field. 

https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/TastOps.fs#L5356

// We can only take the address of mutable values in the same assembly

https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/TastOps.fs#L5367

However the following program will show an error:

open System

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

#nowarn "9"

[<NoComparison;NoEquality>]
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type Vector =
    val V0: int
    val V1: int
    val V2: int
    val V3: int
    val V4: int
    val V5: int
    val V6: int
    val V7: int
    new(v0,v1,v2,v3,v4,v5,v6,v7) = { V0=v0; V1=v1; V2=v2; V3=v3; V4=v4; V5=v5; V6=v6; V7=v7 }

module Main = begin

  let read (x: nativeptr<Vector>) =
    let ptr = x |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<int>
    NativePtr.read ptr

  let get (x: nativeptr<Vector>) i =
    let ptr = x |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<int>
    NativePtr.get ptr i

  [<EntryPoint>]
  let main argv = 
    let s = Vector(0,1,2,3,4,5,6,7)

    //error FS0256: A value must be mutable in order to mutate the contents or take the address of a value type, e.g. 'let mutable x = ...'
    let ptr = &&s |> NativePtr.toNativeInt

    if nativeint(0) <> ptr then
      System.Console.WriteLine("ptr={0}", ptr)
      let tptr = ptr |> NativePtr.ofNativeInt<Vector>
      let v0 = read tptr
      System.Console.WriteLine(v0)
      let v1 = get tptr 1
      System.Console.WriteLine(v1)
      let v2 = get tptr 2
      System.Console.WriteLine(v2)
    else
      System.Console.WriteLine("ptr=0x0")

    0
end

Why the "error FS0256: A value must be mutable in order to mutate the contents or take the address of a value type, e.g. 'let mutable x = ...'" (tastValueMustBeLocalAndMutable) showed in this case? Maybe I miss something but the Vector is an immutable struct and there are no mutation operation on the immutable struct.

    // Give a nice error message for DefinitelyMutates on immutable values, or mutable values in other assemblies
    | Expr.Val(v, _,m) when mut = DefinitelyMutates
       -> 
        if isByrefTy g v.Type then error(Error(FSComp.SR.tastUnexpectedByRef(),m));
        if v.IsMutable then 
            error(Error(FSComp.SR.tastInvalidAddressOfMutableAcrossAssemblyBoundary(),m));
        else 
            error(Error(FSComp.SR.tastValueMustBeLocalAndMutable(),m));

    | _ -> 
        let ty = tyOfExpr g e
        if isStructTy g ty then 
            match mut with 
            | NeverMutates -> ()
            | DefinitelyMutates -> 
                errorR(Error(FSComp.SR.tastInvalidMutationOfConstant(),m));
            | PossiblyMutates -> 
                warning(DefensiveCopyWarning(FSComp.SR.tastValueHasBeenCopied(),m));
        let tmp,_ = mkMutableCompGenLocal m "copyOfStruct" ty
        (fun rest -> mkCompGenLet m tmp e rest), (mkValAddr m (mkLocalValRef tmp))        

https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/TastOps.fs#L5439

@zpodlovics
Copy link
Author

I would be also happy with some kind of (un)safe compiler generated property indexer or a way to add custom built property indexer, so the local pointer will not live longer than the struct itself:

Compiler generated:

  1. add IndexedFieldsAttribute attribute
  2. the compiler checks that the struct itself is blittable (no overlapping fields, sequential layout, pack=1, etc)
  3. the compiler checks that the type for field1...fieldsN is the same
  4. the compiler checks that there is no defined indexed attribute exists for the struct
  5. the compiler generate an indexed property similarly as the custom property indexer

Update:
The fixed may needed here in the custom property indexer example if it not stored on the stack in fixed memory position (eg.: boxed). An ldloca.0 could push the address of the struct to the stack and later store it as a local variable, and assuming the 1)-5) properties are true, the first field will also start at this address [1].

Custom property indexer in C#:

        unsafe public int this[int i] 
        {
            get {
                fixed (Vector* ptr = &this) {
                    if ((i < 0) || (i > 7)) {
                    throw new IndexOutOfRangeException();               
                    } else {
                        int* fptr =&(ptr->V0);
                        return *(fptr + i);
                    }                       
                }
            }
        }

Generated IL from the earlier C# code:

    .method public hidebysig specialname 
           instance default int32 get_Item (int32 i)  cil managed 
    {
        // Method begins at RVA 0x2090
    // Code size 36 (0x24)
    .maxstack 3
    .locals init (
        valuetype CSharpStruct.Vector* pinned   V_0,
        int32*  V_1)
    IL_0000:  ldarg.0 
    IL_0001:  stloc.0 
    IL_0002:  ldarg.1 
    IL_0003:  ldc.i4.0 
    IL_0004:  blt IL_0010

    IL_0009:  ldarg.1 
    IL_000a:  ldc.i4.7 
    IL_000b:  ble IL_0016

    IL_0010:  newobj instance void class [mscorlib]System.IndexOutOfRangeException::'.ctor'()
    IL_0015:  throw 
    IL_0016:  ldloc.0 
    IL_0017:  ldflda int32 CSharpStruct.Vector::V0
    IL_001c:  stloc.1 
    IL_001d:  ldloc.1 
    IL_001e:  ldarg.1 
    IL_001f:  ldc.i4.4 
    IL_0020:  mul 
    IL_0021:  add 
    IL_0022:  ldind.i4 
    IL_0023:  ret 
    } // end of method Vector::get_Item

Is there any way to express this in F#? Compile time code generation with F# or inline IL F# functions is preferred, if not possible the only remaining way is to use some kind of Cecil based post processing [2] [3] or modify the F# compiler.

[1] https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldloca%28v=vs.110%29.aspx
[2] https://github.com/sharpdx/SharpDX/blob/master/Source/SharpDX/Interop.cs
[3] https://github.com/sharpdx/SharpDX/blob/master/Source/Tools/SharpCli/InteropApp.cs#L146

@zpodlovics
Copy link
Author

I figured out an ugly and unsafe way to do it with DynamicMethod code generation. It should be possible to do it (compile time) in F# at least in inline IL (but I failed to figure out the proper inline IL syntax for the "ldarg 0" "ret" IL sequence).

The "unaligned." prefix will needed in non-aligned case according to ECMA 335. This prefix may also need for the new F# initblk, cpblk intrinsics: #16

I.12.6.2 Alignment
[..]
Built-in data types shall be properly aligned, which is defined as follows:
 1-byte, 2-byte, and 4-byte data is properly aligned when it is stored at a 1 -byte, 2-byte,
or 4-byte boundary, respectively.
 8-byte data is properly aligned when it is stored on the same boundary required by the
underlying hardware for atomic access to a native int .
[..]
There is a special prefix instruction, unaligned., that can immediately precede an ldind, stind,
initblk, or cpblk instruction. This prefix indicates that the data can have arbitrary alignment; the
JIT compiler is required to generate code that correctly performs the effect of the instructions
regardless of the actual alignment. Otherwise, if the data is not properly aligned, and no
unaligned. prefix has been specified, executing the instruction can generate unaligned memory
faults or incorrect data.
open System.Runtime.InteropServices
open System.Runtime.CompilerServices
open Microsoft.FSharp.NativeInterop

#nowarn "9"

module StructPtr = begin
    open System.Reflection.Emit    

    type StructPtrDelegate<'T when 'T : unmanaged> = delegate of 'T byref -> nativeint

    let createStructPtrDelegate<'T when 'T : unmanaged>() =
        let dm = new DynamicMethod("structPtr",
                                   typeof<System.Void>,
                                   [| typeof<'T>.MakeByRefType() |])
        let ilGenerator = dm.GetILGenerator()
        ilGenerator.Emit(OpCodes.Ldarg_0)
        ilGenerator.Emit(OpCodes.Ret)
        dm.CreateDelegate(typeof<StructPtrDelegate<'T>>) :?> StructPtrDelegate<'T>

    let structPtr (structPtrDelegate:StructPtrDelegate<'T>) (s: 'T byref) = structPtrDelegate.Invoke(&s)

end

open StructPtr

[<NoComparison;NoEquality>]
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type Vector =  
    val Item0: uint64
    val Item1: uint64
    val Item2: uint64
    val Item3: uint64
    val Item4: uint64
    val Item5: uint64
    val Item6: uint64
    val Item7: uint64
    internal new(
                i0,
                i1,
                i2,
                i3,
                i4,
                i5,
                i6,
                i7
                ) = {
                Item0=i0;
                Item1=i1;
                Item2=i2;
                Item3=i3;
                Item4=i4;
                Item5=i5;
                Item6=i6;
                Item7=i7;
                }

    static member Get = createStructPtrDelegate<Vector>()

    member this.Item
        with get index =
            let ptr = Vector.Get.Invoke(&this)
            let fptr = ptr |> NativePtr.ofNativeInt<uint64>
            if (index < 0) && (index > 7) then
                raise (new System.IndexOutOfRangeException())
            else
                NativePtr.get fptr index           

[<EntryPoint>]
let main argv = 
    printfn "%A" argv

    let x = Vector(1UL,2UL,3UL,4UL,5UL,6UL,7UL,8UL)

    let y0 = x.[0]
    let y1 = x.[1]
    let y2 = x.[2]

    System.Console.WriteLine("[0]: {0}", y0)
    System.Console.WriteLine("[1]: {0}", y1)
    System.Console.WriteLine("[2]: {0}", y2)

    0 // return an integer exit code

@dsyme
Copy link
Contributor

dsyme commented Jan 8, 2016

I suspect this should go to http://fslang.uservoice.com as it looks like a language design issue (?)

@dsyme
Copy link
Contributor

dsyme commented Jan 8, 2016

@zpodlovics Per CONTRIBUTING.md this should go to http://fslang.uservoice.com if it is a language design suggestion, or else to stackoverflow or some other forum if it's an actual question about how to get the address of the struct in question. If you think it's a bug w.r.t. the implementation of the language spec, then please give a smaller repro (a handful of lines should suffice?) Thanks

@dsyme dsyme closed this as completed Jan 8, 2016
@zpodlovics
Copy link
Author

According to the F# Language Specification 3.1 get/use an address of a value type (struct) should be possible:

6.4.5 The AddressOf Operators
Under default definitions, expressions of the following forms are address-of expressions, called byref-address-of expression and nativeptr-address-of expression, respectively:
&expr
&&expr
Such expressions take the address of a mutable local variable, byref-valued argument, field, array element, or static mutable global variable.
For &expr and &&expr , the initial type of the overall expression must be of the form byref and nativeptr respectively, and the expression expr is checked with initial type ty.
The overall expression is elaborated recursively by taking the address of the elaborated form of expr, written AddressOf(expr, DefinitelyMutates), defined in §6.9.4.

[..]

6.9.4 Taking the Address of an Elaborated Expression
When the F# compiler determines the elaborated forms of certain expressions, it must compute a “reference” to an elaborated expression expr, written AddressOf(expr, mutation). The AddressOf operation is used internally within this specification to indicate the elaborated forms of address-of expressions, assignment expressions, and method and property calls on objects of variable and value types.
[..]
If expr has any other form, the elaborated form is &v,where v is a fresh mutable local value that is initialized by adding let v = expr to the overall elaborated form for the entire assignment expression. This initialization is known as a defensive copy of an immutable value. If expr is a struct, expr is copied each time the AddressOf operation is applied, which results in a different address each time. To keep the struct in place, the field that contains it should be marked as mutable.

Repro:

[<Struct>]
type Vector =
    val V0: int
    val V1: int
    new(v0,v1) = { V0=v0; V1=v1 }

[<EntryPoint>]
let main argv = 
    let mutable s = Vector(0,1)
    let sAddr = &&s

    0 

According to the spec, this should be possible. However when I try to compile, it will show the following error:

fsharpc struct.fs

F# Compiler for F# 4.0 (Open Source Edition)
Freely distributed under the Apache 2.0 Open Source License

/tmp/struct.fs(10,17): warning FS0051: The use of native pointers may result in unverifiable .NET IL code

/tmp/struct.fs(10,19): error FS0421: The address of the variable 's' cannot be used at this point

@realvictorprm
Copy link
Contributor

What's the status on this @zpodlovics

@zpodlovics
Copy link
Author

@realvictorprm It seems better now, thank to the relaxed byref rules, hopefully the Span<'T> work will provide additional capabilities.

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

#nowarn "9"
#nowarn "42"

[<NoComparison;NoEquality>]
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>]
type Vector =  
    val Item0: int
    val Item1: int
    val Item2: int
    val Item3: int
    val Item4: int
    val Item5: int
    val Item6: int
    val Item7: int
    internal new(
                i0,
                i1,
                i2,
                i3,
                i4,
                i5,
                i6,
                i7
                ) = {
                Item0=i0;
                Item1=i1;
                Item2=i2;
                Item3=i3;
                Item4=i4;
                Item5=i5;
                Item6=i6;
                Item7=i7;
                }

    [<MethodImpl(MethodImplOptions.NoInlining)>]
    static member RaiseIndexOutOfRangeException() =
        raise (new System.IndexOutOfRangeException())
    
    member this.Item        
        with get index =
            //bound check
            //if uint32(index) >= uint32(7) then
            //    Vector.RaiseIndexOutOfRangeException()
            let ptr = &&this |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<int>
            NativePtr.get ptr index

[<EntryPoint>]
let main argv = 
    printfn "%A" argv

    let x = Vector(1,2,3,4,5,6,7,8)

    let y0 = x.[0]
    let y1 = x.[1]
    let y2 = x.[2]

    System.Console.WriteLine("[0]: {0}", y0)
    System.Console.WriteLine("[1]: {0}", y1)
    System.Console.WriteLine("[2]: {0}", y2)

    0 // return an integer exit code

Now compiles to:

//  Microsoft (R) .NET Framework IL Disassembler.  Version 4.5.22220.0



// Metadata version: v4.0.30319
.assembly extern System.Runtime
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
  .ver 4:2:0:0
}
.assembly extern FSharp.Core
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
  .ver 4:4:1:0
}
.assembly extern System.Runtime.Extensions
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
  .ver 4:2:0:0
}
.assembly extern System.Console
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
  .ver 4:1:0:0
}
.assembly extern System.IO
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
  .ver 4:2:0:0
}
.assembly structptr
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
                                                                                                      int32,
                                                                                                      int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 ) 
  .custom instance void [System.Runtime]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 18 2E 4E 45 54 43 6F 72 65 41 70 70 2C 56   // ....NETCoreApp,V
                                                                                                              65 72 73 69 6F 6E 3D 76 32 2E 30 01 00 54 0E 14   // ersion=v2.0..T..
                                                                                                              46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79   // FrameworkDisplay
                                                                                                              4E 61 6D 65 00 )                                  // Name.

  // --- The following custom attribute is added automatically, do not uncomment -------
  //  .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 03 00 00 00 00 00 ) 

  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.mresource public FSharpSignatureData.structptr
{
  // Offset: 0x00000000 Length: 0x00000821
}
.mresource public FSharpOptimizationData.structptr
{
  // Offset: 0x00000828 Length: 0x00000172
}
.module structptr.exe
// MVID: {5b0ba998-fa0b-8dbe-a745-038398a90b5b}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x00007FE46717B000


// =============== CLASS MEMBERS DECLARATION ===================

.class public abstract auto ansi sealed Program
       extends [System.Runtime]System.Object
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) 
  .class sequential ansi serializable sealed nested public Vector
         extends [System.Runtime]System.ValueType
  {
    .pack 1
    .size 0
    .custom instance void [System.Runtime]System.Reflection.DefaultMemberAttribute::.ctor(string) = ( 01 00 04 49 74 65 6D 00 00 )                      // ...Item..
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoComparisonAttribute::.ctor() = ( 01 00 00 00 ) 
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoEqualityAttribute::.ctor() = ( 01 00 00 00 ) 
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = ( 01 00 00 00 ) 
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) 
    .field assembly int32 Item0@
    .field assembly int32 Item1@
    .field assembly int32 Item2@
    .field assembly int32 Item3@
    .field assembly int32 Item4@
    .field assembly int32 Item5@
    .field assembly int32 Item6@
    .field assembly int32 Item7@
    .method public hidebysig specialname 
            instance int32  get_Item0() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldfld      int32 Program/Vector::Item0@
      IL_0006:  ret
    } // end of method Vector::get_Item0

    .method public hidebysig specialname 
            instance int32  get_Item1() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldfld      int32 Program/Vector::Item1@
      IL_0006:  ret
    } // end of method Vector::get_Item1

    .method public hidebysig specialname 
            instance int32  get_Item2() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldfld      int32 Program/Vector::Item2@
      IL_0006:  ret
    } // end of method Vector::get_Item2

    .method public hidebysig specialname 
            instance int32  get_Item3() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldfld      int32 Program/Vector::Item3@
      IL_0006:  ret
    } // end of method Vector::get_Item3

    .method public hidebysig specialname 
            instance int32  get_Item4() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldfld      int32 Program/Vector::Item4@
      IL_0006:  ret
    } // end of method Vector::get_Item4

    .method public hidebysig specialname 
            instance int32  get_Item5() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldfld      int32 Program/Vector::Item5@
      IL_0006:  ret
    } // end of method Vector::get_Item5

    .method public hidebysig specialname 
            instance int32  get_Item6() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldfld      int32 Program/Vector::Item6@
      IL_0006:  ret
    } // end of method Vector::get_Item6

    .method public hidebysig specialname 
            instance int32  get_Item7() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldfld      int32 Program/Vector::Item7@
      IL_0006:  ret
    } // end of method Vector::get_Item7

    .method assembly specialname rtspecialname 
            instance void  .ctor(int32 i0,
                                 int32 i1,
                                 int32 i2,
                                 int32 i3,
                                 int32 i4,
                                 int32 i5,
                                 int32 i6,
                                 int32 i7) cil managed
    {
      // Code size       62 (0x3e)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldarg.1
      IL_0002:  stfld      int32 Program/Vector::Item0@
      IL_0007:  ldarg.0
      IL_0008:  ldarg.2
      IL_0009:  stfld      int32 Program/Vector::Item1@
      IL_000e:  ldarg.0
      IL_000f:  ldarg.3
      IL_0010:  stfld      int32 Program/Vector::Item2@
      IL_0015:  ldarg.0
      IL_0016:  ldarg.s    i3
      IL_0018:  stfld      int32 Program/Vector::Item3@
      IL_001d:  ldarg.0
      IL_001e:  ldarg.s    i4
      IL_0020:  stfld      int32 Program/Vector::Item4@
      IL_0025:  ldarg.0
      IL_0026:  ldarg.s    i5
      IL_0028:  stfld      int32 Program/Vector::Item5@
      IL_002d:  ldarg.0
      IL_002e:  ldarg.s    i6
      IL_0030:  stfld      int32 Program/Vector::Item6@
      IL_0035:  ldarg.0
      IL_0036:  ldarg.s    i7
      IL_0038:  stfld      int32 Program/Vector::Item7@
      IL_003d:  ret
    } // end of method Vector::.ctor

    .method public static !!a  RaiseIndexOutOfRangeException<a>() cil managed noinlining
    {
      // Code size       6 (0x6)
      .maxstack  8
      IL_0000:  newobj     instance void [System.Runtime]System.IndexOutOfRangeException::.ctor()
      IL_0005:  throw
    } // end of method Vector::RaiseIndexOutOfRangeException

    .method public hidebysig specialname 
            instance int32  get_Item(int32 index) cil managed
    {
      // Code size       19 (0x13)
      .maxstack  5
      .locals init (native int V_0)
      IL_0000:  ldarg.0
      IL_0001:  stloc.0
      IL_0002:  ldloc.0
      IL_0003:  ldarg.1
      IL_0004:  conv.i
      IL_0005:  sizeof     [System.Runtime]System.Int32
      IL_000b:  mul
      IL_000c:  add
      IL_000d:  ldobj      [System.Runtime]System.Int32
      IL_0012:  ret
    } // end of method Vector::get_Item

    .property instance int32 Item0()
    {
      .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,
                                                                                                  int32) = ( 01 00 04 00 00 00 00 00 00 00 00 00 ) 
      .get instance int32 Program/Vector::get_Item0()
    } // end of property Vector::Item0
    .property instance int32 Item1()
    {
      .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,
                                                                                                  int32) = ( 01 00 04 00 00 00 01 00 00 00 00 00 ) 
      .get instance int32 Program/Vector::get_Item1()
    } // end of property Vector::Item1
    .property instance int32 Item2()
    {
      .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,
                                                                                                  int32) = ( 01 00 04 00 00 00 02 00 00 00 00 00 ) 
      .get instance int32 Program/Vector::get_Item2()
    } // end of property Vector::Item2
    .property instance int32 Item3()
    {
      .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,
                                                                                                  int32) = ( 01 00 04 00 00 00 03 00 00 00 00 00 ) 
      .get instance int32 Program/Vector::get_Item3()
    } // end of property Vector::Item3
    .property instance int32 Item4()
    {
      .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,
                                                                                                  int32) = ( 01 00 04 00 00 00 04 00 00 00 00 00 ) 
      .get instance int32 Program/Vector::get_Item4()
    } // end of property Vector::Item4
    .property instance int32 Item5()
    {
      .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,
                                                                                                  int32) = ( 01 00 04 00 00 00 05 00 00 00 00 00 ) 
      .get instance int32 Program/Vector::get_Item5()
    } // end of property Vector::Item5
    .property instance int32 Item6()
    {
      .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,
                                                                                                  int32) = ( 01 00 04 00 00 00 06 00 00 00 00 00 ) 
      .get instance int32 Program/Vector::get_Item6()
    } // end of property Vector::Item6
    .property instance int32 Item7()
    {
      .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,
                                                                                                  int32) = ( 01 00 04 00 00 00 07 00 00 00 00 00 ) 
      .get instance int32 Program/Vector::get_Item7()
    } // end of property Vector::Item7
    .property instance int32 Item(int32)
    {
      .get instance int32 Program/Vector::get_Item(int32)
    } // end of property Vector::Item
  } // end of class Vector

  .method public static int32  main(string[] argv) cil managed
  {
    .entrypoint
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor() = ( 01 00 00 00 ) 
    // Code size       122 (0x7a)
    .maxstack  10
    .locals init (class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string[],class [FSharp.Core]Microsoft.FSharp.Core.Unit>,class [System.Runtime.Extensions]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit> V_0,
             valuetype Program/Vector V_1,
             int32 V_2,
             int32 V_3,
             int32 V_4)
    IL_0000:  ldstr      "%A"
    IL_0005:  newobj     instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string[],class [FSharp.Core]Microsoft.FSharp.Core.Unit>,class [System.Runtime.Extensions]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit,string[]>::.ctor(string)
    IL_000a:  stloc.0
    IL_000b:  call       class [System.IO]System.IO.TextWriter [System.Console]System.Console::get_Out()
    IL_0010:  ldloc.0
    IL_0011:  call       !!0 [FSharp.Core]Microsoft.FSharp.Core.PrintfModule::PrintFormatLineToTextWriter<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string[],class [FSharp.Core]Microsoft.FSharp.Core.Unit>>(class [System.Runtime.Extensions]System.IO.TextWriter,
                                                                                                                                                                                                                         class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0,class [System.Runtime.Extensions]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
    IL_0016:  ldarg.0
    IL_0017:  callvirt   instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string[],class [FSharp.Core]Microsoft.FSharp.Core.Unit>::Invoke(!0)
    IL_001c:  pop
    IL_001d:  ldc.i4.1
    IL_001e:  ldc.i4.2
    IL_001f:  ldc.i4.3
    IL_0020:  ldc.i4.4
    IL_0021:  ldc.i4.5
    IL_0022:  ldc.i4.6
    IL_0023:  ldc.i4.7
    IL_0024:  ldc.i4.8
    IL_0025:  newobj     instance void Program/Vector::.ctor(int32,
                                                             int32,
                                                             int32,
                                                             int32,
                                                             int32,
                                                             int32,
                                                             int32,
                                                             int32)
    IL_002a:  stloc.1
    IL_002b:  ldloca.s   V_1
    IL_002d:  ldc.i4.0
    IL_002e:  call       instance int32 Program/Vector::get_Item(int32)
    IL_0033:  stloc.2
    IL_0034:  ldloca.s   V_1
    IL_0036:  ldc.i4.1
    IL_0037:  call       instance int32 Program/Vector::get_Item(int32)
    IL_003c:  stloc.3
    IL_003d:  ldloca.s   V_1
    IL_003f:  ldc.i4.2
    IL_0040:  call       instance int32 Program/Vector::get_Item(int32)
    IL_0045:  stloc.s    V_4
    IL_0047:  ldstr      "[0]: {0}"
    IL_004c:  ldloc.2
    IL_004d:  box        [System.Runtime]System.Int32
    IL_0052:  call       void [System.Console]System.Console::WriteLine(string,
                                                                        object)
    IL_0057:  ldstr      "[1]: {0}"
    IL_005c:  ldloc.3
    IL_005d:  box        [System.Runtime]System.Int32
    IL_0062:  call       void [System.Console]System.Console::WriteLine(string,
                                                                        object)
    IL_0067:  ldstr      "[2]: {0}"
    IL_006c:  ldloc.s    V_4
    IL_006e:  box        [System.Runtime]System.Int32
    IL_0073:  call       void [System.Console]System.Console::WriteLine(string,
                                                                        object)
    IL_0078:  ldc.i4.0
    IL_0079:  ret
  } // end of method Program::main

} // end of class Program

.class private abstract auto ansi sealed '<StartupCode$structptr>'.$Program
       extends [System.Runtime]System.Object
{
} // end of class '<StartupCode$structptr>'.$Program

.class private abstract auto ansi sealed '<StartupCode$structptr>.$.NETCoreApp,Version=v2.0'.AssemblyAttributes
       extends [System.Runtime]System.Object
{
} // end of class '<StartupCode$structptr>.$.NETCoreApp,Version=v2.0'.AssemblyAttributes


// =============================================================

// *********** DISASSEMBLY COMPLETE ***********************

Now with some nativeptr "magic" it's possible to provide readonly array like structures. The readonly field addressof (````&this.Item0) still not working, hopefully the Span<'T> will change that. The get_Item``` now looks better, the ```sizeof [mscorlib]System.Int32``` should already known (C# use this), and ```ldobj [mscorlib]System.Int32``` could be ```ldind.i4 ``` (C# use this). The main method now avoid the struct copy and use ```ldloca.s``` before you call the indexer method. However more detailed study will require to determine what is the more efficient way (benchmark, check the jit generated code, performance counters, etc).

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

No branches or pull requests

3 participants