Skip to content

Commit 3c7fd74

Browse files
committed
### iOS compat (#70)
* ShallowEqualityComparer checks if DynamicMethod actually works * current fallback simply returns false for equals and 0 for hash (TODO: check for a better way)
1 parent 508a2b2 commit 3c7fd74

2 files changed

Lines changed: 136 additions & 94 deletions

File tree

src/FSharp.Data.Adaptive/ShallowEquality.fs

Lines changed: 118 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,36 @@ open System.Runtime.CompilerServices
4040
type private ShallowEqDelegate<'a> = delegate of 'a * 'a -> bool
4141
type private ShallowHashDelegate<'a> = delegate of 'a -> int
4242

43+
[<AutoOpen>]
44+
module ``DynamicMethod Extensions`` =
45+
let private canEmit =
46+
try
47+
let meth =
48+
DynamicMethod(
49+
"testEmitMethod",
50+
MethodAttributes.Static ||| MethodAttributes.Public,
51+
CallingConventions.Standard,
52+
typeof<int>,
53+
[| typeof<int> |],
54+
typeof<obj>,
55+
true
56+
)
57+
let il = meth.GetILGenerator()
58+
il.Emit(OpCodes.Ldarg_0)
59+
il.Emit(OpCodes.Ldc_I4, 10)
60+
il.Emit(OpCodes.Mul)
61+
il.Emit(OpCodes.Ret)
62+
63+
let f = meth.CreateDelegate(typeof<System.Func<int, int>>) |> unbox<System.Func<int, int>>
64+
65+
f.Invoke(1) = 10
66+
with _ ->
67+
false
68+
69+
type DynamicMethod with
70+
static member IsSupported = canEmit
71+
72+
4373
[<AbstractClass; Sealed>]
4474
type private HashCodeHelpers private() =
4575

@@ -79,103 +109,111 @@ type ShallowEqualityComparer<'a> private() =
79109
if isUnmanaged then
80110
Unchecked.hash
81111
elif typ.IsValueType then
82-
let fields =
83-
typ.GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic ||| BindingFlags.Public)
112+
if DynamicMethod.IsSupported then
113+
let fields =
114+
typ.GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic ||| BindingFlags.Public)
84115

85-
let meth =
86-
DynamicMethod(
87-
"shallowHash",
88-
MethodAttributes.Static ||| MethodAttributes.Public,
89-
CallingConventions.Standard,
90-
typeof<int>,
91-
[| typeof<'a> |],
92-
typeof<'a>,
93-
true
94-
)
116+
let meth =
117+
DynamicMethod(
118+
"shallowHash",
119+
MethodAttributes.Static ||| MethodAttributes.Public,
120+
CallingConventions.Standard,
121+
typeof<int>,
122+
[| typeof<'a> |],
123+
typeof<'a>,
124+
true
125+
)
95126

96127

97-
let il = meth.GetILGenerator()
98-
let l = il.DeclareLocal(typeof<int>)
99-
100-
// l <- 0
101-
il.Emit(OpCodes.Ldc_I4_0)
102-
il.Emit(OpCodes.Stloc, l)
103-
104-
for f in fields do
105-
let self = self.MakeGenericType [| f.FieldType |]
106-
let hash =
107-
self.GetMethod(
108-
"ShallowHashCode",
109-
BindingFlags.Static ||| BindingFlags.NonPublic ||| BindingFlags.Public,
110-
System.Type.DefaultBinder,
111-
[| f.FieldType |],
112-
null
113-
)
128+
let il = meth.GetILGenerator()
129+
let l = il.DeclareLocal(typeof<int>)
114130

115-
il.Emit(OpCodes.Ldarg_0)
116-
il.Emit(OpCodes.Ldfld, f)
117-
il.EmitCall(OpCodes.Call, hash, null)
118-
il.Emit(OpCodes.Ldloc, l)
119-
il.EmitCall(OpCodes.Call, HashCodeHelpers.CombineMethod, null)
131+
// l <- 0
132+
il.Emit(OpCodes.Ldc_I4_0)
120133
il.Emit(OpCodes.Stloc, l)
134+
135+
for f in fields do
136+
let self = self.MakeGenericType [| f.FieldType |]
137+
let hash =
138+
self.GetMethod(
139+
"ShallowHashCode",
140+
BindingFlags.Static ||| BindingFlags.NonPublic ||| BindingFlags.Public,
141+
System.Type.DefaultBinder,
142+
[| f.FieldType |],
143+
null
144+
)
145+
146+
il.Emit(OpCodes.Ldarg_0)
147+
il.Emit(OpCodes.Ldfld, f)
148+
il.EmitCall(OpCodes.Call, hash, null)
149+
il.Emit(OpCodes.Ldloc, l)
150+
il.EmitCall(OpCodes.Call, HashCodeHelpers.CombineMethod, null)
151+
il.Emit(OpCodes.Stloc, l)
121152

122-
il.Emit(OpCodes.Ldloc, l)
123-
il.Emit(OpCodes.Ret)
153+
il.Emit(OpCodes.Ldloc, l)
154+
il.Emit(OpCodes.Ret)
124155

125-
let del = meth.CreateDelegate(typeof<ShallowHashDelegate<'a>>) |> unbox<ShallowHashDelegate<'a>>
126-
del.Invoke
156+
let del = meth.CreateDelegate(typeof<ShallowHashDelegate<'a>>) |> unbox<ShallowHashDelegate<'a>>
157+
del.Invoke
158+
else
159+
// TODO: any better way??
160+
fun (v : 'a) -> 0
127161
else
128162
fun (v : 'a) -> RuntimeHelpers.GetHashCode(v :> obj)
129163

130164
static let equals =
131165
if isUnmanaged then
132166
Unchecked.equals
133167
elif typ.IsValueType then
134-
let fields =
135-
typ.GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic ||| BindingFlags.Public)
136-
137-
let meth =
138-
DynamicMethod(
139-
"shallowEquals",
140-
MethodAttributes.Static ||| MethodAttributes.Public,
141-
CallingConventions.Standard,
142-
typeof<bool>,
143-
[| typeof<'a>; typeof<'a> |],
144-
typeof<'a>,
145-
true
146-
)
147-
148-
let il = meth.GetILGenerator()
149-
let falseLabel = il.DefineLabel()
150-
151-
for f in fields do
152-
let self = self.MakeGenericType [| f.FieldType |]
153-
let eq =
154-
self.GetMethod(
155-
"ShallowEquals",
156-
BindingFlags.Static ||| BindingFlags.NonPublic ||| BindingFlags.Public,
157-
System.Type.DefaultBinder,
158-
[| f.FieldType; f.FieldType |],
159-
null
168+
if DynamicMethod.IsSupported then
169+
let fields =
170+
typ.GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic ||| BindingFlags.Public)
171+
172+
let meth =
173+
DynamicMethod(
174+
"shallowEquals",
175+
MethodAttributes.Static ||| MethodAttributes.Public,
176+
CallingConventions.Standard,
177+
typeof<bool>,
178+
[| typeof<'a>; typeof<'a> |],
179+
typeof<'a>,
180+
true
160181
)
161182

162-
il.Emit(OpCodes.Ldarg_0)
163-
il.Emit(OpCodes.Ldfld, f)
183+
let il = meth.GetILGenerator()
184+
let falseLabel = il.DefineLabel()
185+
186+
for f in fields do
187+
let self = self.MakeGenericType [| f.FieldType |]
188+
let eq =
189+
self.GetMethod(
190+
"ShallowEquals",
191+
BindingFlags.Static ||| BindingFlags.NonPublic ||| BindingFlags.Public,
192+
System.Type.DefaultBinder,
193+
[| f.FieldType; f.FieldType |],
194+
null
195+
)
196+
197+
il.Emit(OpCodes.Ldarg_0)
198+
il.Emit(OpCodes.Ldfld, f)
164199

165-
il.Emit(OpCodes.Ldarg_1)
166-
il.Emit(OpCodes.Ldfld, f)
167-
168-
il.EmitCall(OpCodes.Call, eq, null)
169-
il.Emit(OpCodes.Brfalse, falseLabel)
170-
171-
il.Emit(OpCodes.Ldc_I4_1)
172-
il.Emit(OpCodes.Ret)
173-
il.MarkLabel(falseLabel)
174-
il.Emit(OpCodes.Ldc_I4_0)
175-
il.Emit(OpCodes.Ret)
176-
177-
let del = meth.CreateDelegate(typeof<ShallowEqDelegate<'a>>) |> unbox<ShallowEqDelegate<'a>>
178-
fun (a : 'a) (b : 'a) -> del.Invoke(a, b)
200+
il.Emit(OpCodes.Ldarg_1)
201+
il.Emit(OpCodes.Ldfld, f)
202+
203+
il.EmitCall(OpCodes.Call, eq, null)
204+
il.Emit(OpCodes.Brfalse, falseLabel)
205+
206+
il.Emit(OpCodes.Ldc_I4_1)
207+
il.Emit(OpCodes.Ret)
208+
il.MarkLabel(falseLabel)
209+
il.Emit(OpCodes.Ldc_I4_0)
210+
il.Emit(OpCodes.Ret)
211+
212+
let del = meth.CreateDelegate(typeof<ShallowEqDelegate<'a>>) |> unbox<ShallowEqDelegate<'a>>
213+
fun (a : 'a) (b : 'a) -> del.Invoke(a, b)
214+
else
215+
/// TODO: better way?
216+
fun (a : 'a) (b : 'a) -> false
179217
else
180218
fun (a : 'a) (b : 'a) -> System.Object.ReferenceEquals(a :> obj, b :> obj)
181219

src/Test/FSharp.Data.Adaptive.Tests/Utilities/Generators.fs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,24 @@ module Helpers =
2929
open System.Reflection.Emit
3030

3131
let private invoker (mi : MethodInfo) =
32-
let dargs = [|typeof<obj>|]
33-
let m = DynamicMethod("invoker", MethodAttributes.Static ||| MethodAttributes.Public, CallingConventions.Standard, typeof<obj>, dargs, typeof<obj>, true)
34-
35-
let il = m.GetILGenerator()
36-
il.Emit(OpCodes.Ldarg, 0)
37-
il.EmitCall(OpCodes.Callvirt, mi, null)
38-
if mi.ReturnType.IsValueType then il.Emit(OpCodes.Box, mi.ReturnType)
39-
il.Emit(OpCodes.Ret)
40-
41-
let invoke =
42-
m.CreateDelegate(typeof<Func<obj, obj>>)
43-
|> unbox<Func<obj, obj>>
44-
45-
invoke.Invoke
32+
if DynamicMethod.IsSupported then
33+
let dargs = [|typeof<obj>|]
34+
let m = DynamicMethod("invoker", MethodAttributes.Static ||| MethodAttributes.Public, CallingConventions.Standard, typeof<obj>, dargs, typeof<obj>, true)
35+
36+
let il = m.GetILGenerator()
37+
il.Emit(OpCodes.Ldarg, 0)
38+
il.EmitCall(OpCodes.Callvirt, mi, null)
39+
if mi.ReturnType.IsValueType then il.Emit(OpCodes.Box, mi.ReturnType)
40+
il.Emit(OpCodes.Ret)
41+
42+
let invoke =
43+
m.CreateDelegate(typeof<Func<obj, obj>>)
44+
|> unbox<Func<obj, obj>>
45+
46+
invoke.Invoke
47+
else
48+
fun (o : obj) -> mi.Invoke(o, [||])
49+
4650

4751
let private getMethod1 =
4852
let cache = System.Collections.Concurrent.ConcurrentDictionary<Type * Type, obj -> obj>()

0 commit comments

Comments
 (0)