Last active
September 27, 2015 17:34
-
-
Save zpodlovics/80e12e2de35cf73e6e03 to your computer and use it in GitHub Desktop.
Proof of concept FSharp Struct DU
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Author: Zoltan Podlovics | |
/// License: Apache License, Version 2.0, https://www.apache.org/licenses/ | |
/// Description: Proof of concept struct discriminated union mapping to struct union (currently limited to unmanaged | |
/// (blittable) types) | |
/// | |
/// Note: currently only works with unmanaged (blittable) types and solution or workaround may or may not exists for | |
/// managed types. Using it with managed types will throw System.TypeLoadException "because it contains an object field | |
/// at offset 8 that is incorrectly aligned or overlapped by a non-object field" when used it with managed types in | |
/// fields. | |
/// | |
/// Usage: | |
/// fsharpc fsharpstructdiscriminatedunion.fs | |
open System.Runtime.InteropServices | |
open System.Runtime.CompilerServices | |
open Microsoft.FSharp.NativeInterop | |
#nowarn "9" | |
#nowarn "42" | |
type Foo = | |
| Bar of int | |
| Bar64 of int64 | |
| Baz of int * int | |
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>] | |
type StructBar = | |
val Value: int | |
new(v) = { Value = v } | |
override this.ToString() = | |
System.String.Format("StructBar[Value={0}]", this.Value) | |
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>] | |
type StructBar64 = | |
val Value: int64 | |
new(v) = { Value = v } | |
override this.ToString() = | |
System.String.Format("StructBar64[Value={0}]", this.Value) | |
//Note: this generic struct is NOT unmanaged (blittable)! | |
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>] | |
type StructTuple2<'T1,'T2> = | |
val Item1: 'T1 | |
val Item2: 'T2 | |
new(i1,i2) = { Item1=i1; Item2=i2 } | |
override this.ToString() = | |
System.String.Format("StructTuple2[Item1={0},Item2={1}]", this.Item1, this.Item2) | |
//Note: this struct is unmanaged (blittable)! | |
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>] | |
type StructTupleIntInt = | |
val Item1: int | |
val Item2: int | |
new(i1,i2) = { Item1=i1; Item2=i2 } | |
override this.ToString() = | |
System.String.Format("StructTupleIntInt[Item1={0},Item2={1}]", this.Item1, this.Item2) | |
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1)>] | |
type StructBaz = | |
val Value: StructTupleIntInt | |
new(v) = { Value=v } | |
new(v1,v2) = { Value=StructTupleIntInt(v1,v2) } | |
override this.ToString() = | |
System.String.Format("StructBaz[Value={0}]", this.Value.ToString()) | |
//Note: calculate max size of all possible case but structlayout size field require literal | |
[<Literal>] | |
let DUMaxSize = 8 | |
// Note: required for GetValue<'T> as a maximum size struct | |
[<Struct; StructLayout(LayoutKind.Sequential, Pack=1, Size=DUMaxSize)>] | |
type StructFooMax = | |
val Value: byte | |
//Note: workaround for field initialization order issue -> each du case will be copied to the dummy StructFooMax | |
let structCopy<'T,'U when 'T : unmanaged and 'U: unmanaged> (src: 'T) = | |
let srcPtr = NativePtr.stackalloc 1 : nativeptr<'T> in | |
let dstPtr = srcPtr |> NativePtr.toNativeInt |> NativePtr.ofNativeInt<'U> | |
NativePtr.write srcPtr src; | |
NativePtr.read dstPtr | |
[<Struct; StructLayout(LayoutKind.Explicit)>] | |
type ImmutableStructFoo = | |
[<FieldOffset(0)>] | |
val TypeHandle: System.RuntimeTypeHandle | |
[<FieldOffset(8)>] | |
val ValueAsStructBar: StructBar | |
[<FieldOffset(8)>] | |
val ValueAsStructBar64: StructBar64 | |
[<FieldOffset(8)>] | |
val ValueAsStructBaz: StructBaz | |
[<FieldOffset(8)>] | |
val Value: StructFooMax | |
static member StructBar = typeof<StructBar> | |
static member StructBar64 = typeof<StructBar64> | |
static member StructBaz = typeof<StructBaz> | |
//Note: check FSharp spec for field initialization order | |
//Note: the FSharp generated IL will always seems to initialize in the defined order in this case: ValueAsStructBar, ValueAsStructBar64, ValueAsStructBaz, Value | |
new(v: StructBar) = { TypeHandle=ImmutableStructFoo.StructBar.TypeHandle; ValueAsStructBar64=StructBar64(); ValueAsStructBaz=StructBaz(); ValueAsStructBar=v; Value=structCopy v } | |
new(v: StructBar64) = { TypeHandle=ImmutableStructFoo.StructBar64.TypeHandle; ValueAsStructBar=StructBar(); ValueAsStructBaz=StructBaz(); ValueAsStructBar64=v; Value=structCopy v } | |
new(v: StructBaz) = { TypeHandle=ImmutableStructFoo.StructBaz.TypeHandle; ValueAsStructBar=StructBar(); ValueAsStructBar64=StructBar64(); ValueAsStructBaz=v; Value=structCopy v } | |
//Note: Use Marshal.AllocHGlobal for verifiable bytecode | |
//Note: use a modified version of NativePtr.read IL which could cast to a specified type passed as argument | |
member this.GetValue<'T>() = | |
let ptr = NativePtr.stackalloc<StructFooMax> 1 |> NativePtr.toNativeInt | |
Marshal.StructureToPtr(this.Value, ptr, false); | |
Marshal.PtrToStructure(ptr, System.Type.GetTypeFromHandle(this.TypeHandle)) | |
member this.GetValueNative<'T when 'T: unmanaged>() = | |
structCopy<StructFooMax,'T> this.Value | |
override this.ToString() = | |
System.String.Format("ImmutableStructFoo[Type={0}]", System.Type.GetTypeFromHandle(this.TypeHandle).ToString()) | |
[<EntryPoint>] | |
let main argv = | |
printfn "%A" argv | |
let ibar = ImmutableStructFoo(StructBar(1)) | |
let ibar64 = ImmutableStructFoo(StructBar64(2L)) | |
let ibaz = ImmutableStructFoo(StructBaz(4,8)) | |
let ibarValue = ibar.GetValue<_>() | |
let ibar64Value = ibar64.GetValue<_>() | |
let ibazValue = ibaz.GetValue<_>() | |
System.Console.WriteLine("Immutable Bar: {0}", ibar.ToString()); | |
System.Console.WriteLine("Immutable Bar64: {0}", ibar64.ToString()); | |
System.Console.WriteLine("Immutable Baz: {0}", ibaz.ToString()); | |
System.Console.WriteLine("Immutable BarValue: {0}", ibarValue.ToString()); | |
System.Console.WriteLine("Immutable Bar64Value: {0}", ibar64Value.ToString()); | |
System.Console.WriteLine("Immutable BazValue: {0}", ibazValue.ToString()); | |
0 // return an integer exit code | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment