From f3ac895512ce93712ddb8b160ee9fa7cfa55f8ec Mon Sep 17 00:00:00 2001
From: Paul Westcott
Date: Tue, 9 Jun 2020 12:02:02 +1000
Subject: [PATCH] non-boxing equality and comparison
---
src/fsharp/FSharp.Core/local.fs | 33 +-
src/fsharp/FSharp.Core/prim-types.fs | 1304 +++++++++++------
src/fsharp/FSharp.Core/prim-types.fsi | 35 +-
src/fsharp/FSharp.Core/reflect.fs | 231 +--
src/fsharp/Optimizer.fs | 44 +-
src/fsharp/TcGlobals.fs | 7 +
.../FSharp.Core/ComparersRegression.fs | 74 +-
.../SurfaceArea.coreclr.fs | 9 +
.../SurfaceArea.net40.fs | 9 +
.../Linq101Joins01.il.bsl | 95 +-
.../GenericComparison/Compare07.il.bsl | 82 +-
.../GenericComparison/Equals06.il.bsl | 82 +-
.../GenericComparison/Equals07.il.bsl | 47 +-
.../GenericComparison/Equals08.il.bsl | 47 +-
.../GenericComparison/Hash09.il.bsl | 82 +-
.../GenericComparison/Hash10.il.bsl | 41 +-
.../GenericComparison/Hash11.il.bsl | 41 +-
17 files changed, 1299 insertions(+), 964 deletions(-)
diff --git a/src/fsharp/FSharp.Core/local.fs b/src/fsharp/FSharp.Core/local.fs
index 3d812fc72c4..30bf88846b5 100644
--- a/src/fsharp/FSharp.Core/local.fs
+++ b/src/fsharp/FSharp.Core/local.fs
@@ -987,8 +987,12 @@ module internal Array =
open System
- let inline fastComparerForArraySort<'t when 't : comparison> () =
- LanguagePrimitives.FastGenericComparerCanBeNull<'t>
+ let inline getInternalComparer<'t when 't : comparison> () =
+ // Previously a "comparer" was returned that could be null, which was for optimized Array.Sort
+ // but we now mainly return Comparer.Default (and FastGenericComparerInternal more so)
+ // which is also optimized in Array.Sort
+ // ** this comment can be destroyed sometime in the future, it is just as a breadcrumb for review **
+ LanguagePrimitives.FastGenericComparerInternal<'t>
// The input parameter should be checked by callers if necessary
let inline zeroCreateUnchecked (count:int) =
@@ -1086,26 +1090,26 @@ module internal Array =
let keys = zeroCreateUnchecked array.Length
for i = 0 to array.Length - 1 do
keys.[i] <- projection array.[i]
- Array.Sort<_, _>(keys, array, fastComparerForArraySort())
+ Array.Sort<_, _>(keys, array, getInternalComparer())
let unstableSortInPlace (array : array<'T>) =
let len = array.Length
if len < 2 then ()
- else Array.Sort<_>(array, fastComparerForArraySort())
+ else Array.Sort<_>(array, getInternalComparer())
- let stableSortWithKeysAndComparer (cFast:IComparer<'Key>) (c:IComparer<'Key>) (array:array<'T>) (keys:array<'Key>) =
+ let stableSortWithKeysAndComparer (c:IComparer<'Key>) (array:array<'T>) (keys:array<'Key>) =
// 'places' is an array or integers storing the permutation performed by the sort
let places = zeroCreateUnchecked array.Length
for i = 0 to array.Length - 1 do
places.[i] <- i
- System.Array.Sort<_, _>(keys, places, cFast)
+ System.Array.Sort<_, _>(keys, places, c)
// 'array2' is a copy of the original values
let array2 = (array.Clone() :?> array<'T>)
// Walk through any chunks where the keys are equal
let mutable i = 0
let len = array.Length
- let intCompare = fastComparerForArraySort()
+ let intCompare = getInternalComparer()
while i < len do
let mutable j = i
@@ -1120,9 +1124,8 @@ module internal Array =
i <- j
let stableSortWithKeys (array:array<'T>) (keys:array<'Key>) =
- let cFast = fastComparerForArraySort()
- let c = LanguagePrimitives.FastGenericComparer<'Key>
- stableSortWithKeysAndComparer cFast c array keys
+ let c = getInternalComparer()
+ stableSortWithKeysAndComparer c array keys
let stableSortInPlaceBy (projection: 'T -> 'U) (array : array<'T>) =
let len = array.Length
@@ -1138,13 +1141,11 @@ module internal Array =
let len = array.Length
if len < 2 then ()
else
- let cFast = LanguagePrimitives.FastGenericComparerCanBeNull<'T>
- match cFast with
- | null ->
+ if LanguagePrimitives.EquivalentForStableAndUnstableSort<'T> then
// An optimization for the cases where the keys and values coincide and do not have identity, e.g. are integers
// In this case an unstable sort is just as good as a stable sort (and faster)
Array.Sort<_, _>(array, null)
- | _ ->
+ else
// 'keys' is an array storing the projected keys
let keys = (array.Clone() :?> array<'T>)
stableSortWithKeys array keys
@@ -1155,7 +1156,7 @@ module internal Array =
let keys = (array.Clone() :?> array<'T>)
let comparer = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(comparer)
let c = { new IComparer<'T> with member __.Compare(x, y) = comparer.Invoke(x, y) }
- stableSortWithKeysAndComparer c c array keys
+ stableSortWithKeysAndComparer c array keys
let inline subUnchecked startIndex count (array : 'T[]) =
let res = zeroCreateUnchecked count : 'T[]
@@ -1204,4 +1205,4 @@ module internal Seq =
while (e.MoveNext()) do res <- e.Current
ValueSome(res)
else
- ValueNone
\ No newline at end of file
+ ValueNone
diff --git a/src/fsharp/FSharp.Core/prim-types.fs b/src/fsharp/FSharp.Core/prim-types.fs
index d0d608f1190..0b039ffdd59 100644
--- a/src/fsharp/FSharp.Core/prim-types.fs
+++ b/src/fsharp/FSharp.Core/prim-types.fs
@@ -787,8 +787,452 @@ namespace Microsoft.FSharp.Core
let anyToStringShowingNull x = anyToString "null" x
- module HashCompare =
-
+ module internal Reflection =
+ let inline flagsOr<'a> (lhs:'a) (rhs:'a) =
+ (# "or" lhs rhs : 'a #)
+
+ let inline flagsAnd<'a> (lhs:'a) (rhs:'a) =
+ (# "and" lhs rhs : 'a #)
+
+ let inline flagsContains<'a when 'a : equality> (flags:'a) (mask:'a) (value:'a) =
+ (flagsAnd flags mask).Equals value
+
+ let inline flagsIsSet<'a when 'a : equality> (flags:'a) (value:'a) =
+ flagsContains flags value value
+
+ let instancePropertyFlags = flagsOr BindingFlags.GetProperty BindingFlags.Instance
+ let staticFieldFlags = flagsOr BindingFlags.GetField BindingFlags.Static
+ let staticMethodFlags = BindingFlags.Static
+
+ let tupleNames = [|
+ "System.Tuple`1"
+ "System.Tuple`2"
+ "System.Tuple`3"
+ "System.Tuple`4"
+ "System.Tuple`5"
+ "System.Tuple`6"
+ "System.Tuple`7"
+ "System.Tuple`8"
+ "System.Tuple"
+ "System.ValueTuple`1"
+ "System.ValueTuple`2"
+ "System.ValueTuple`3"
+ "System.ValueTuple`4"
+ "System.ValueTuple`5"
+ "System.ValueTuple`6"
+ "System.ValueTuple`7"
+ "System.ValueTuple`8"
+ "System.ValueTuple"
+ |]
+
+ let simpleTupleNames = [|
+ "Tuple`1"
+ "Tuple`2"
+ "Tuple`3"
+ "Tuple`4"
+ "Tuple`5"
+ "Tuple`6"
+ "Tuple`7"
+ "Tuple`8"
+ "ValueTuple`1"
+ "ValueTuple`2"
+ "ValueTuple`3"
+ "ValueTuple`4"
+ "ValueTuple`5"
+ "ValueTuple`6"
+ "ValueTuple`7"
+ "ValueTuple`8"
+ |]
+
+ let isTupleType (typ:Type) =
+ // We need to be careful that we only rely typ.IsGenericType, typ.Namespace and typ.Name here.
+ //
+ // Historically the FSharp.Core reflection utilities get used on implementations of
+ // System.Type that don't have functionality such as .IsEnum and .FullName fully implemented.
+ // This happens particularly over TypeBuilderInstantiation types in the ProvideTypes implementation of System.TYpe
+ // used in F# type providers.
+ typ.IsGenericType &&
+ System.String.Equals(typ.Namespace, "System") &&
+ Array.Exists (simpleTupleNames, Predicate typ.Name.StartsWith)
+
+ let assemblyName = typeof.Assembly.GetName().Name
+ let _ = assert (System.String.Equals (assemblyName, "FSharp.Core"))
+ let cmaName = typeof.FullName
+
+ let tryFindCompilationMappingAttributeFromData (attrs:System.Collections.Generic.IList, res:byref) : bool =
+ match attrs with
+ | null -> false
+ | _ ->
+ let mutable found = false
+ for a in attrs do
+ if a.Constructor.DeclaringType.FullName.Equals cmaName then
+ let args = a.ConstructorArguments
+ let flags =
+ match args.Count with
+ | 1 -> ((let x = args.[0] in x.Value :?> SourceConstructFlags), 0, 0)
+ | 2 -> ((let x = args.[0] in x.Value :?> SourceConstructFlags), (let x = args.[1] in x.Value :?> int), 0)
+ | 3 -> ((let x = args.[0] in x.Value :?> SourceConstructFlags), (let x = args.[1] in x.Value :?> int), (let x = args.[2] in x.Value :?> int))
+ | _ -> (SourceConstructFlags.None, 0, 0)
+ res <- flags
+ found <- true
+ found
+
+ let findCompilationMappingAttributeFromData attrs =
+ let mutable x = unsafeDefault<_>
+ match tryFindCompilationMappingAttributeFromData (attrs, &x) with
+ | false -> raise (Exception "no compilation mapping attribute")
+ | true -> x
+
+ let hasCustomEquality (typ:Type) =
+ let arr = typ.GetCustomAttributes (typeof, false)
+ arr.Length > 0
+
+ let hasCustomComparison (typ:Type) =
+ let arr = typ.GetCustomAttributes (typeof, false)
+ arr.Length > 0
+
+ let tryFindCompilationMappingAttribute (attrs:obj[], res:byref) : bool =
+ match attrs with
+ | null | [||] -> false
+ | [| :? CompilationMappingAttribute as a |] ->
+ res <- a.SourceConstructFlags, a.SequenceNumber, a.VariantNumber
+ true
+ | _ -> raise (System.InvalidOperationException (SR.GetString(SR.multipleCompilationMappings)))
+
+ let findCompilationMappingAttribute (attrs:obj[]) =
+ let mutable x = unsafeDefault<_>
+ match tryFindCompilationMappingAttribute (attrs, &x) with
+ | false -> raise (Exception "no compilation mapping attribute")
+ | true -> x
+
+ let tryFindCompilationMappingAttributeFromType (typ:Type, res:byref) : bool =
+ let assem = typ.Assembly
+ if (not (obj.ReferenceEquals(assem, null))) && assem.ReflectionOnly then
+ tryFindCompilationMappingAttributeFromData (typ.GetCustomAttributesData(), &res)
+ else
+ tryFindCompilationMappingAttribute (typ.GetCustomAttributes (typeof,false), &res)
+
+ let tryFindCompilationMappingAttributeFromMemberInfo (info:MemberInfo, res:byref) : bool =
+ let assem = info.DeclaringType.Assembly
+ if (not (obj.ReferenceEquals (assem, null))) && assem.ReflectionOnly then
+ tryFindCompilationMappingAttributeFromData (info.GetCustomAttributesData(), &res)
+ else
+ tryFindCompilationMappingAttribute (info.GetCustomAttributes (typeof,false), &res)
+
+ let findCompilationMappingAttributeFromMemberInfo (info:MemberInfo) =
+ let assem = info.DeclaringType.Assembly
+ if (not (obj.ReferenceEquals (assem, null))) && assem.ReflectionOnly then
+ findCompilationMappingAttributeFromData (info.GetCustomAttributesData())
+ else
+ findCompilationMappingAttribute (info.GetCustomAttributes (typeof,false))
+
+ let tryFindSourceConstructFlagsOfType (typ:Type, res:byref) : bool =
+ let mutable x = unsafeDefault<_>
+ if tryFindCompilationMappingAttributeFromType (typ, &x) then
+ let flags,_n,_vn = x
+ res <- flags
+ true
+ else
+ false
+
+ let isKnownType (typ:Type, bindingFlags:BindingFlags, knownType:SourceConstructFlags) =
+ let mutable flags = unsafeDefault<_>
+ match tryFindSourceConstructFlagsOfType (typ, &flags) with
+ | false -> false
+ | true ->
+ (flagsContains flags SourceConstructFlags.KindMask knownType) &&
+ // We see private representations only if BindingFlags.NonPublic is set
+ (if flagsIsSet flags SourceConstructFlags.NonPublicRepresentation then
+ flagsIsSet bindingFlags BindingFlags.NonPublic
+ else
+ true)
+
+ let isRecordType (typ:Type, bindingFlags:BindingFlags) = isKnownType (typ, bindingFlags, SourceConstructFlags.RecordType)
+ let isObjectType (typ:Type, bindingFlags:BindingFlags) = isKnownType (typ, bindingFlags, SourceConstructFlags.ObjectType)
+ let isUnionType (typ:Type, bindingFlags:BindingFlags) = isKnownType (typ, bindingFlags, SourceConstructFlags.SumType)
+
+ let isFieldProperty (prop : PropertyInfo) =
+ let mutable res = unsafeDefault<_>
+ match tryFindCompilationMappingAttributeFromMemberInfo(prop:>MemberInfo, &res) with
+ | false -> false
+ | true ->
+ let (flags,_n,_vn) = res
+ flagsContains flags SourceConstructFlags.KindMask SourceConstructFlags.Field
+
+ let sequenceNumberOfMember (x:MemberInfo) = let (_,n,_) = findCompilationMappingAttributeFromMemberInfo x in n
+ let variantNumberOfMember (x:MemberInfo) = let (_,_,vn) = findCompilationMappingAttributeFromMemberInfo x in vn
+
+ // Although this funciton is called sortFreshArray (and was so in it's previously life in reflect.fs)
+ // it does not create a fresh array, but rather uses the existing array.
+ let sortFreshArray (f:'a->int) (arr:'a[]) =
+ let comparer = System.Collections.Generic.Comparer.Default
+ System.Array.Sort (arr, {
+ new IComparer<'a> with
+ member __.Compare (lhs:'a, rhs:'a) =
+ comparer.Compare (f lhs, f rhs) })
+ arr
+
+ let fieldPropsOfRecordType (typ:Type, bindingFlags) =
+ let properties = typ.GetProperties (flagsOr instancePropertyFlags bindingFlags)
+ let fields = System.Array.FindAll (properties, Predicate isFieldProperty)
+ sortFreshArray sequenceNumberOfMember fields
+
+ let getUnionTypeTagNameMap (typ:Type,bindingFlags:BindingFlags) : (int*string)[] =
+ let enumTyp = typ.GetNestedType ("Tags", bindingFlags)
+ // Unions with a singleton case do not get a Tags type (since there is only one tag), hence enumTyp may be null in this case
+ match enumTyp with
+ | null ->
+ let methods = typ.GetMethods (flagsOr staticMethodFlags bindingFlags)
+ let maybeTagNames =
+ Array.ConvertAll (methods, Converter (fun minfo ->
+ let mutable res = unsafeDefault<_>
+ match tryFindCompilationMappingAttributeFromMemberInfo (minfo:>MemberInfo, &res) with
+ | false -> unsafeDefault<_>
+ | true ->
+ let flags,n,_vn = res
+ if flagsContains flags SourceConstructFlags.KindMask SourceConstructFlags.UnionCase then
+ // chop "get_" or "New" off the front
+ let nm =
+ let nm = minfo.Name
+ if nm.StartsWith "get_" then nm.Substring 4
+ elif nm.StartsWith "New" then nm.Substring 3
+ else nm
+ (n, nm)
+ else
+ unsafeDefault<_> ))
+ Array.FindAll (maybeTagNames, Predicate (fun maybeTagName -> not (obj.ReferenceEquals (maybeTagName, null))))
+ | _ ->
+ let fields = enumTyp.GetFields (flagsOr staticFieldFlags bindingFlags)
+ let filtered = Array.FindAll (fields, (fun (f:FieldInfo) -> f.IsStatic && f.IsLiteral))
+ let sorted = sortFreshArray (fun (f:FieldInfo) -> (f.GetValue null) :?> int) filtered
+ Array.ConvertAll (sorted, Converter (fun tagfield -> (tagfield.GetValue(null) :?> int),tagfield.Name))
+
+ let getUnionCasesTyp (typ: Type, _bindingFlags) =
+#if CASES_IN_NESTED_CLASS
+ let casesTyp = typ.GetNestedType("Cases", bindingFlags)
+ if casesTyp.IsGenericTypeDefinition then casesTyp.MakeGenericType(typ.GetGenericArguments())
+ else casesTyp
+#else
+ typ
+#endif
+
+ let getUnionCaseTyp (typ: Type, tag: int, bindingFlags) =
+ let tagFields = getUnionTypeTagNameMap(typ,bindingFlags)
+ let tagField = let _,f = Array.Find (tagFields, Predicate (fun (i,_) -> i = tag)) in f
+
+ if tagFields.Length = 1 then
+ typ
+ else
+ // special case: two-cased DU annotated with CompilationRepresentation(UseNullAsTrueValue)
+ // in this case it will be compiled as one class: return self type for non-nullary case and null for nullary
+ let isTwoCasedDU =
+ if tagFields.Length = 2 then
+ match typ.GetCustomAttributes(typeof, false) with
+ | [|:? CompilationRepresentationAttribute as attr|] ->
+ flagsIsSet attr.Flags CompilationRepresentationFlags.UseNullAsTrueValue
+ | _ -> false
+ else
+ false
+ if isTwoCasedDU then
+ typ
+ else
+ let casesTyp = getUnionCasesTyp (typ, bindingFlags)
+ let caseTyp = casesTyp.GetNestedType(tagField, bindingFlags) // if this is null then the union is nullary
+ match caseTyp with
+ | null -> null
+ | _ when caseTyp.IsGenericTypeDefinition -> caseTyp.MakeGenericType(casesTyp.GetGenericArguments())
+ | _ -> caseTyp
+
+ let fieldsPropsOfUnionCase(typ:Type, tag:int, bindingFlags) =
+ // Lookup the type holding the fields for the union case
+ let caseTyp = getUnionCaseTyp (typ, tag, bindingFlags)
+ let caseTyp = match caseTyp with null -> typ | _ -> caseTyp
+ let properties = caseTyp.GetProperties (flagsOr instancePropertyFlags bindingFlags)
+ let filtered = Array.FindAll (properties, Predicate (fun p -> if isFieldProperty p then (variantNumberOfMember (p:>MemberInfo)) = tag else false))
+ sortFreshArray (fun (p:PropertyInfo) -> sequenceNumberOfMember p) filtered
+
+ let getAllInstanceFields (typ:Type) =
+ let fields = typ.GetFields (flagsOr BindingFlags.Instance (flagsOr BindingFlags.Public BindingFlags.NonPublic))
+ Array.ConvertAll (fields, Converter (fun p -> p.FieldType))
+
+ module HashCompare =
+ let isArray (ty:Type) =
+ ty.IsArray || (typeof.IsAssignableFrom ty)
+
+ let canUseDotnetDefaultComparisonOrEquality isCustom hasStructuralInterface stringsRequireHandling er (rootType:Type) =
+ let processed = System.Collections.Generic.HashSet ()
+
+ let bindingPublicOrNonPublic =
+ Reflection.flagsOr BindingFlags.Public BindingFlags.NonPublic
+
+ let rec isSuitableNullableTypeOrNotNullable (ty:Type) =
+ // although nullables not explicitly handled previously, they need special handling
+ // due to the implicit casting to their underlying generic type (i.e. could be a float)
+ let isNullableType =
+ ty.IsGenericType
+ && ty.GetGenericTypeDefinition().Equals typedefof>
+ if isNullableType then
+ checkType 0 (ty.GetGenericArguments ())
+ else
+ true
+
+ and isSuitableTupleType (ty:Type) =
+ ty.IsValueType && // Tuple<...> don't have implementation, but ValueTuple<...> does
+ Reflection.isTupleType ty &&
+ checkType 0 (ty.GetGenericArguments ())
+
+ and isSuitableStructType (ty:Type) =
+ ty.IsValueType &&
+ Reflection.isObjectType (ty, bindingPublicOrNonPublic) &&
+ (not (isCustom ty)) &&
+ checkType 0 (Reflection.getAllInstanceFields ty)
+
+ and isSuitableRecordType (ty:Type) =
+ Reflection.isRecordType (ty, bindingPublicOrNonPublic) &&
+ (not (isCustom ty)) &&
+ ( let fields = Reflection.fieldPropsOfRecordType (ty, bindingPublicOrNonPublic)
+ let fieldTypes = Array.ConvertAll (fields, Converter (fun f -> f.PropertyType))
+ checkType 0 fieldTypes)
+
+ and isSuitableUnionType (ty:Type) =
+ Reflection.isUnionType (ty, bindingPublicOrNonPublic) &&
+ (not (isCustom ty)) &&
+ ( let cases = Reflection.getUnionTypeTagNameMap (ty, bindingPublicOrNonPublic)
+ let rec checkCases idx =
+ if idx = cases.Length then true
+ else
+ let tag,_ = get cases idx
+ let fields = Reflection.fieldsPropsOfUnionCase (ty, tag, bindingPublicOrNonPublic)
+ let fieldTypes = Array.ConvertAll (fields, Converter (fun f -> f.PropertyType))
+ if checkType 0 fieldTypes then
+ checkCases (idx+1)
+ else
+ false
+ checkCases 0)
+
+ and checkType idx (types:Type[]) =
+ if idx = types.Length then true
+ else
+ let ty = get types idx
+ if not (processed.Add ty) then
+ checkType (idx+1) types
+ else
+ ty.IsSealed // covers enum and value types; derived ref types might implement from hasStructuralInterface
+ && not (isArray ty)
+ && (not stringsRequireHandling || (not (ty.Equals typeof)))
+ && (er || (not (ty.Equals typeof)))
+ && (er || (not (ty.Equals typeof)))
+ && isSuitableNullableTypeOrNotNullable ty
+ && ((not (hasStructuralInterface ty))
+ || isSuitableTupleType ty
+ || isSuitableStructType ty
+ || isSuitableRecordType ty
+ || isSuitableUnionType ty)
+ && checkType (idx+1) types
+
+ checkType 0 [|rootType|]
+
+ //-------------------------------------------------------------------------
+ // LanguagePrimitives.HashCompare: HASHING.
+ //-------------------------------------------------------------------------
+ let defaultHashNodes = 18
+
+ /// The implementation of IEqualityComparer, using depth-limited for hashing and PER semantics for NaN equality.
+ type CountLimitedHasherPER(sz:int) =
+ []
+ val mutable nodeCount : int
+
+ member x.Fresh() =
+ if (System.Threading.Interlocked.CompareExchange(&(x.nodeCount), sz, 0) = 0) then
+ x
+ else
+ new CountLimitedHasherPER(sz)
+
+ interface IEqualityComparer
+
+ /// The implementation of IEqualityComparer, using unlimited depth for hashing and ER semantics for NaN equality.
+ type UnlimitedHasherER() =
+ interface IEqualityComparer
+
+ /// The implementation of IEqualityComparer, using unlimited depth for hashing and PER semantics for NaN equality.
+ type UnlimitedHasherPER() =
+ interface IEqualityComparer
+
+
+ /// The unique object for unlimited depth for hashing and ER semantics for equality.
+ let fsEqualityComparerUnlimitedHashingER = UnlimitedHasherER()
+
+ /// The unique object for unlimited depth for hashing and PER semantics for equality.
+ let fsEqualityComparerUnlimitedHashingPER = UnlimitedHasherPER()
+
+ let inline HashCombine nr x y = (x <<< 1) + y + 631 * nr
+
+ let inline ArrayHashing<'element,'array when 'array :> System.Array> get lowerBound (f:'element->int) (x:'array) : int =
+ let rec loop acc i =
+ if i < lowerBound then acc
+ else loop (HashCombine i acc (f (get x i))) (i-1)
+
+ let lastIdx =
+ let upperBound = lowerBound+defaultHashNodes
+ match lowerBound+x.Length-1 with
+ | oversized when oversized > upperBound -> upperBound
+ | good -> good
+
+ loop 0 lastIdx
+
+ let GenericHashObjArray (iec:System.Collections.IEqualityComparer) (x:obj[]) = ArrayHashing get 0 iec.GetHashCode x
+ let GenericHashByteArray (x:byte[]) = ArrayHashing get 0 intOfByte x
+ let GenericHashInt32Array (x:int32[]) = ArrayHashing get 0 (fun x -> x) x
+ let GenericHashInt64Array (x:int64[]) = ArrayHashing get 0 int32 x
+
+ // special case - arrays do not by default have a decent structural hashing function
+ let GenericHashArbArray (iec : System.Collections.IEqualityComparer) (x: System.Array) : int =
+ match x.Rank with
+ | 1 -> ArrayHashing (fun a i -> a.GetValue i) (x.GetLowerBound 0) iec.GetHashCode x
+ | _ -> HashCombine 10 (x.GetLength(0)) (x.GetLength(1))
+
+ // Core implementation of structural hashing, corresponds to pseudo-code in the
+ // F# Language spec. Searches for the IStructuralHash interface, otherwise uses GetHashCode().
+ // Arrays are structurally hashed through a separate technique.
+ //
+ // "iec" is either fsEqualityComparerUnlimitedHashingER, fsEqualityComparerUnlimitedHashingPER or a CountLimitedHasherPER.
+ let rec GenericHashParamObj (iec : System.Collections.IEqualityComparer) (x: obj) : int =
+ match x with
+ | null -> 0
+ | (:? System.Array as a) ->
+ // due to the rules of the CLI type system, array casts are "assignment compatible"
+ // see: https://blogs.msdn.microsoft.com/ericlippert/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent/
+ // this means that the cast and comparison for byte will also handle sbyte, int32 handle uint32,
+ // and int64 handle uint64. The hash code of an individual array element is different for the different
+ // types, but it is irrelevant for the creation of the hash code - but this is to be replicated in
+ // the tryGetFSharpArrayEqualityComparer function.
+ match a with
+ | :? (obj[]) as oa -> GenericHashObjArray iec oa
+ | :? (byte[]) as ba -> GenericHashByteArray ba
+ | :? (int[]) as ba -> GenericHashInt32Array ba
+ | :? (int64[]) as ba -> GenericHashInt64Array ba
+ | _ -> GenericHashArbArray iec a
+ | :? IStructuralEquatable as a ->
+ a.GetHashCode(iec)
+ | _ ->
+ x.GetHashCode()
+
+ /// Direct call to GetHashCode on the string type
+ let inline HashString (s:string) =
+ match s with
+ | null -> 0
+ | _ -> s.GetHashCode ()
+
+ // from mscorlib v4.0.30319
+ let inline HashChar (x:char) = (# "or" (# "shl" x 16 : int #) x : int #)
+ let inline HashSByte (x:sbyte) = (# "xor" (# "shl" x 8 : int #) x : int #)
+ let inline HashInt16 (x:int16) = (# "or" (# "conv.u2" x : int #) (# "shl" x 16 : int #) : int #)
+ let inline HashInt64 (x:int64) = (# "xor" (# "conv.i4" x : int #) (# "conv.i4" (# "shr" x 32 : int #) : int #) : int #)
+ let inline HashUInt64 (x:uint64) = (# "xor" (# "conv.i4" x : int #) (# "conv.i4" (# "shr.un" x 32 : int #) : int #) : int #)
+ let inline HashIntPtr (x:nativeint) = (# "conv.i4" (# "conv.u8" x : uint64 #) : int #)
+ let inline HashUIntPtr (x:unativeint) = (# "and" (# "conv.i4" (# "conv.u8" x : uint64 #) : int #) 0x7fffffff : int #)
+
+
//-------------------------------------------------------------------------
// LanguagePrimitives.HashCompare: Physical Equality
//-------------------------------------------------------------------------
@@ -836,6 +1280,17 @@ namespace Microsoft.FSharp.Core
/// This exception should never be observed by user code.
let NaNException = new System.Exception()
+ let inline ArrayComparison<'T> (f:'T->'T->int) (x:'T[]) (y:'T[]) : int =
+ let lenx = x.Length
+ let leny = y.Length
+ let rec loop c i =
+ if c <> 0 then Math.Sign c
+ elif i = lenx then 0
+ else loop (f (get x i) (get y i)) (i+1)
+ loop (lenx-leny) 0
+
+ let GenericComparisonByteArray (x:byte[]) (y:byte[]) : int = ArrayComparison (fun x y -> (# "conv.i4" x : int32 #)-(# "conv.i4" y : int32 #)) x y
+
/// Implements generic comparison between two objects. This corresponds to the pseudo-code in the F#
/// specification. The treatment of NaNs is governed by "comp".
let rec GenericCompare (comp:GenericComparer) (xobj:obj,yobj:obj) =
@@ -851,11 +1306,11 @@ namespace Microsoft.FSharp.Core
// Permit structural comparison on arrays
| (:? System.Array as arr1),_ ->
match arr1,yobj with
- // Fast path
- | (:? (obj[]) as arr1), (:? (obj[]) as arr2) ->
+ | (:? (obj[]) as arr1), (:? (obj[]) as arr2)->
GenericComparisonObjArrayWithComparer comp arr1 arr2
- // Fast path
- | (:? (byte[]) as arr1), (:? (byte[]) as arr2) ->
+ // The additional equality check is required here because .net treats byte[] and sbyte[] as cast-compatible
+ // (but comparison is different)
+ | (:? (byte[]) as arr1), (:? (byte[]) as arr2) when typeof.Equals (arr1.GetType ()) && typeof.Equals (arr2.GetType ()) ->
GenericComparisonByteArray arr1 arr2
| _, (:? System.Array as arr2) ->
GenericComparisonArbArrayWithComparer comp arr1 arr2
@@ -969,35 +1424,8 @@ namespace Microsoft.FSharp.Core
checkN k (int64 baseIdx) 0L (x.GetLongLength(k))
check 0
- /// optimized case: Core implementation of structural comparison on object arrays.
and GenericComparisonObjArrayWithComparer (comp:GenericComparer) (x:obj[]) (y:obj[]) : int =
- let lenx = x.Length
- let leny = y.Length
- let c = intOrder lenx leny
- if c <> 0 then c
- else
- let mutable i = 0
- let mutable res = 0
- while i < lenx do
- let c = GenericCompare comp ((get x i), (get y i))
- if c <> 0 then (res <- c; i <- lenx)
- else i <- i + 1
- res
-
- /// optimized case: Core implementation of structural comparison on arrays.
- and GenericComparisonByteArray (x:byte[]) (y:byte[]) : int =
- let lenx = x.Length
- let leny = y.Length
- let c = intOrder lenx leny
- if c <> 0 then c
- else
- let mutable i = 0
- let mutable res = 0
- while i < lenx do
- let c = byteOrder (get x i) (get y i)
- if c <> 0 then (res <- c; i <- lenx)
- else i <- i + 1
- res
+ ArrayComparison (fun x y -> GenericCompare comp (x, y)) x y
type GenericComparer with
interface System.Collections.IComparer with
@@ -1009,6 +1437,198 @@ namespace Microsoft.FSharp.Core
/// The unique object for comparing values in ER mode (where "0" is returned when NaNs are compared)
let fsComparerER = GenericComparer(false)
+ let isStructuralComparable (ty:Type) = typeof.IsAssignableFrom ty
+ let isValueTypeStructuralComparable (ty:Type) = isStructuralComparable ty && ty.IsValueType
+
+ let canUseDefaultComparer er (rootType:Type) =
+ // "Default" equality for strings is culturally sensitive, so needs special handling
+ canUseDotnetDefaultComparisonOrEquality Reflection.hasCustomComparison isStructuralComparable true er rootType
+
+ type ComparisonUsage =
+ | ERUsage = 0
+ | PERUsage = 1
+ | LessThanUsage = 2
+ | GreaterThanUsage = 3
+
+ []
+ let LessThanUsageReturnFalse = 1
+ []
+ let GreaterThanUsageReturnFalse = -1
+
+ let inline signedComparer<'T> () =
+ box { new Comparer<'T>() with
+ member __.Compare (x,y) =
+ if (# "clt" x y : bool #) then -1
+ else (# "cgt" x y : int #) }
+
+ let inline unsignedComparer<'T> () =
+ box { new Comparer<'T>() with
+ member __.Compare (x,y) =
+ if (# "clt.un" x y : bool #) then -1
+ else (# "cgt.un" x y : int #) }
+
+ let inline floatingPointComparer<'T> onNaN =
+ box { new Comparer<'T>() with
+ member __.Compare (x,y) =
+ if (# "clt" x y : bool #) then -1
+ elif (# "cgt" x y : bool #) then 1
+ elif (# "ceq" x y : bool #) then 0
+ else onNaN () }
+
+ let tryGetFSharpComparer (usage:ComparisonUsage) (externalUse:bool) (ty:Type) : obj =
+ match usage, externalUse, ty with
+ | ComparisonUsage.ERUsage, _, ty when ty.Equals typeof -> box Comparer.Default
+ | ComparisonUsage.ERUsage, _, ty when ty.Equals typeof -> box Comparer.Default
+
+ | ComparisonUsage.PERUsage, _, ty when ty.Equals typeof -> floatingPointComparer (fun () -> raise NaNException)
+ | ComparisonUsage.LessThanUsage, _, ty when ty.Equals typeof -> floatingPointComparer (fun () -> LessThanUsageReturnFalse)
+ | ComparisonUsage.GreaterThanUsage, _, ty when ty.Equals typeof -> floatingPointComparer (fun () -> GreaterThanUsageReturnFalse)
+
+ | ComparisonUsage.PERUsage, _, ty when ty.Equals typeof -> floatingPointComparer (fun () -> raise NaNException)
+ | ComparisonUsage.LessThanUsage, _, ty when ty.Equals typeof -> floatingPointComparer (fun () -> LessThanUsageReturnFalse)
+ | ComparisonUsage.GreaterThanUsage, _, ty when ty.Equals typeof -> floatingPointComparer (fun () -> GreaterThanUsageReturnFalse)
+
+ // the implemention of Comparer.Default returns a current culture specific comparer
+ | _, _, ty when ty.Equals typeof ->
+ box { new Comparer() with
+ member __.Compare (x,y) =
+ System.String.CompareOrdinal (x, y) }
+
+ | _, _, ty when ty.Equals typeof -> unsignedComparer ()
+ | _, _, ty when ty.Equals typeof -> signedComparer ()
+
+ // these are used as external facing comparers for compatability (they always return -1/0/+1)
+ | _, true, ty when ty.Equals typeof -> unsignedComparer ()
+ | _, true, ty when ty.Equals typeof -> signedComparer ()
+ | _, true, ty when ty.Equals typeof -> signedComparer ()
+ | _, true, ty when ty.Equals typeof -> unsignedComparer ()
+ | _, true, ty when ty.Equals typeof -> unsignedComparer ()
+
+ | _ -> null
+
+ let inline nullableComparer<'a when 'a : null> compare =
+ box { new Comparer<'a>() with
+ member __.Compare (x,y) =
+ match x, y with
+ | null, null -> 0
+ | null, _ -> -1
+ | _, null -> 1
+ | _ -> compare x y }
+
+ let tryGetFSharpArrayComparer (ty:Type) comparer : obj =
+ if ty.Equals typeof then nullableComparer (fun x y -> GenericComparisonObjArrayWithComparer comparer x y)
+ elif ty.Equals typeof then nullableComparer GenericComparisonByteArray
+ else null
+
+ let arrayComparer<'T> comparer =
+ match tryGetFSharpArrayComparer typeof<'T> comparer with
+ | :? Comparer<'T> as arrayComparer -> arrayComparer
+ | _ ->
+ { new Comparer<'T>() with
+ member __.Compare (x, y) =
+ match box x, box y with
+ | null, null -> 0
+ | null, _ -> -1
+ | _, null -> 1
+ | (:? System.Array as arr1), (:? System.Array as arr2) -> GenericComparisonArbArrayWithComparer comparer arr1 arr2
+ | _ -> raise (Exception "invalid logic - expected System.Array") }
+
+ let structuralComparer<'T> comparer =
+ { new Comparer<'T>() with
+ member __.Compare (x,y) =
+ match box x, box y with
+ | null, null -> 0
+ | null, _ -> -1
+ | _, null -> 1
+ | (:? IStructuralComparable as x1), yobj -> x1.CompareTo (yobj, comparer)
+ | _ -> raise (Exception "invalid logic - expected IStructuralEquatable") }
+
+ let structuralComparerValueType<'T> comparer =
+ { new Comparer<'T>() with
+ member __.Compare (x,y) =
+ ((box x):?>IStructuralComparable).CompareTo (y, comparer) }
+
+ let unknownComparer<'T> comparer =
+ { new Comparer<'T>() with
+ member __.Compare (x,y) =
+ GenericCompare comparer (box x, box y) }
+
+ // this wrapper is used with the comparison operators to cause a false result when a NaNException
+ // has been thrown somewhere in the tested objects hierarchy
+ let maybeNaNExceptionComparer<'T> (comparer:Comparer<'T>) valueToCauseFalse =
+ { new Comparer<'T>() with
+ member __.Compare (x,y) =
+ try
+ comparer.Compare (x,y)
+ with
+ e when System.Runtime.CompilerServices.RuntimeHelpers.Equals(e, NaNException) -> valueToCauseFalse }
+
+ let getGenericComparison<'T> usage externalUse =
+ let er = match usage with ComparisonUsage.ERUsage -> true | _ -> false
+
+ match tryGetFSharpComparer usage externalUse typeof<'T> with
+ | :? Comparer<'T> as comparer -> comparer
+ | _ when canUseDefaultComparer er typeof<'T> -> Comparer<'T>.Default
+ | _ ->
+ if er then
+ if isArray typeof<'T> then arrayComparer fsComparerER
+ elif isValueTypeStructuralComparable typeof<'T> then structuralComparerValueType fsComparerER
+ elif isStructuralComparable typeof<'T> then structuralComparer fsComparerER
+ else unknownComparer fsComparerER
+ else
+ let comparer =
+ if isArray typeof<'T> then arrayComparer fsComparerPER
+ elif isValueTypeStructuralComparable typeof<'T> then structuralComparerValueType fsComparerPER
+ elif isStructuralComparable typeof<'T> then structuralComparer fsComparerPER
+ else unknownComparer fsComparerPER
+
+ match usage with
+ | ComparisonUsage.LessThanUsage -> maybeNaNExceptionComparer comparer LessThanUsageReturnFalse
+ | ComparisonUsage.GreaterThanUsage -> maybeNaNExceptionComparer comparer GreaterThanUsageReturnFalse
+ | _ -> comparer
+
+ /// As an optimization, determine if a fast unstable sort can be used with equivalent results
+ let equivalentForStableAndUnstableSort (ty:Type) =
+ ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+ || ty.Equals(typeof)
+
+ []
+ type FSharpComparer_ER<'T> private () =
+ static let comparer = getGenericComparison<'T> ComparisonUsage.ERUsage true
+ static member Comparer = comparer
+
+ []
+ type FSharpComparer_InternalUse_ER<'T> private () =
+ static let equivalentForStableAndUnstableSort = equivalentForStableAndUnstableSort typeof<'T>
+ static let comparer = getGenericComparison<'T> ComparisonUsage.ERUsage false
+ static member Comparer = comparer
+ static member EquivalentForStableAndUnstableSort = equivalentForStableAndUnstableSort
+
+ []
+ type FSharpComparer_PER<'T> private () =
+ static let comparer = getGenericComparison<'T> ComparisonUsage.PERUsage false
+ static member Comparer = comparer
+
+ []
+ type FSharpComparer_ForLessThanComparison<'T> private () =
+ static let comparer = getGenericComparison<'T> ComparisonUsage.LessThanUsage false
+ static member Comparer = comparer
+
+ []
+ type FSharpComparer_ForGreaterThanComparison<'T> private () =
+ static let comparer = getGenericComparison<'T> ComparisonUsage.GreaterThanUsage false
+ static member Comparer = comparer
+
/// Compare two values of the same generic type, using "comp".
//
// "comp" is assumed to be either fsComparerPER or fsComparerER (and hence 'Compare' is implemented via 'GenericCompare').
@@ -1016,7 +1636,12 @@ namespace Microsoft.FSharp.Core
// NOTE: the compiler optimizer is aware of this function and devirtualizes in the
// cases where it is known how a particular type implements generic comparison.
let GenericComparisonWithComparerIntrinsic<'T> (comp:System.Collections.IComparer) (x:'T) (y:'T) : int =
- comp.Compare(box x, box y)
+ if obj.ReferenceEquals (comp, fsComparerER) then
+ FSharpComparer_InternalUse_ER.Comparer.Compare (x, y)
+ elif obj.ReferenceEquals (comp, fsComparerPER) then
+ FSharpComparer_PER.Comparer.Compare (x, y)
+ else
+ comp.Compare (box x, box y)
/// Compare two values of the same generic type, in either PER or ER mode, but include static optimizations
/// for various well-known cases.
@@ -1059,45 +1684,28 @@ namespace Microsoft.FSharp.Core
// The compiler optimizer is aware of this function (see use of generic_comparison_inner_vref in opt.fs)
// and devirtualizes calls to it based on "T".
let GenericComparisonIntrinsic<'T> (x:'T) (y:'T) : int =
- GenericComparisonWithComparerIntrinsic (fsComparerER :> IComparer) x y
+ FSharpComparer_ER.Comparer.Compare (x, y)
/// Generic less-than. Uses comparison implementation in PER mode but catches
/// the local exception that is thrown when NaN's are compared.
let GenericLessThanIntrinsic (x:'T) (y:'T) =
- try
- (# "clt" (GenericComparisonWithComparerIntrinsic fsComparerPER x y) 0 : bool #)
- with
- | e when System.Runtime.CompilerServices.RuntimeHelpers.Equals(e, NaNException) -> false
-
+ (# "clt" (FSharpComparer_ForLessThanComparison.Comparer.Compare (x, y)) 0 : bool #)
/// Generic greater-than. Uses comparison implementation in PER mode but catches
/// the local exception that is thrown when NaN's are compared.
let GenericGreaterThanIntrinsic (x:'T) (y:'T) =
- try
- (# "cgt" (GenericComparisonWithComparerIntrinsic fsComparerPER x y) 0 : bool #)
- with
- | e when System.Runtime.CompilerServices.RuntimeHelpers.Equals(e, NaNException) -> false
-
+ (# "cgt" (FSharpComparer_ForGreaterThanComparison.Comparer.Compare (x, y)) 0 : bool #)
/// Generic greater-than-or-equal. Uses comparison implementation in PER mode but catches
/// the local exception that is thrown when NaN's are compared.
let GenericGreaterOrEqualIntrinsic (x:'T) (y:'T) =
- try
- (# "cgt" (GenericComparisonWithComparerIntrinsic fsComparerPER x y) (-1) : bool #)
- with
- | e when System.Runtime.CompilerServices.RuntimeHelpers.Equals(e, NaNException) -> false
-
-
+ (# "cgt" (FSharpComparer_ForGreaterThanComparison.Comparer.Compare (x, y)) -1 : bool #)
/// Generic less-than-or-equal. Uses comparison implementation in PER mode but catches
/// the local exception that is thrown when NaN's are compared.
let GenericLessOrEqualIntrinsic (x:'T) (y:'T) =
- try
- (# "clt" (GenericComparisonWithComparerIntrinsic fsComparerPER x y) 1 : bool #)
- with
- | e when System.Runtime.CompilerServices.RuntimeHelpers.Equals(e, NaNException) -> false
-
+ (# "clt" (FSharpComparer_ForLessThanComparison.Comparer.Compare (x, y)) 1 : bool #)
/// Compare two values of the same generic type, in ER mode, with static optimizations
/// for known cases.
@@ -1218,99 +1826,26 @@ namespace Microsoft.FSharp.Core
// LanguagePrimitives.HashCompare: EQUALITY
//-------------------------------------------------------------------------
-
- /// optimized case: Core implementation of structural equality on arrays.
- let GenericEqualityByteArray (x:byte[]) (y:byte[]) : bool=
+ let inline ArrayEquality<'T> (f:'T->'T->bool) (x:'T[]) (y:'T[]) : bool =
let lenx = x.Length
let leny = y.Length
- let c = (lenx = leny)
- if not c then c
- else
- let mutable i = 0
- let mutable res = true
- while i < lenx do
- let c = byteEq (get x i) (get y i)
- if not c then (res <- false; i <- lenx)
- else i <- i + 1
- res
+ let rec loop i =
+ if i = lenx then true
+ elif f (get x i) (get y i) then loop (i+1)
+ else false
+ (lenx = leny) && loop 0
- /// optimized case: Core implementation of structural equality on arrays.
- let GenericEqualityInt32Array (x:int[]) (y:int[]) : bool=
- let lenx = x.Length
- let leny = y.Length
- let c = (lenx = leny)
- if not c then c
- else
- let mutable i = 0
- let mutable res = true
- while i < lenx do
- let c = int32Eq (get x i) (get y i)
- if not c then (res <- false; i <- lenx)
- else i <- i + 1
- res
-
- /// optimized case: Core implementation of structural equality on arrays
- let GenericEqualitySingleArray er (x:float32[]) (y:float32[]) : bool=
- let lenx = x.Length
- let leny = y.Length
- let f32eq x y = if er && not(float32Eq x x) && not(float32Eq y y) then true else (float32Eq x y)
- let c = (lenx = leny)
- if not c then c
- else
- let mutable i = 0
- let mutable res = true
- while i < lenx do
- let c = f32eq (get x i) (get y i)
- if not c then (res <- false; i <- lenx)
- else i <- i + 1
- res
-
- /// optimized case: Core implementation of structural equality on arrays.
- let GenericEqualityDoubleArray er (x:float[]) (y:float[]) : bool=
- let lenx = x.Length
- let leny = y.Length
- let c = (lenx = leny)
- let feq x y = if er && not(floatEq x x) && not(floatEq y y) then true else (floatEq x y)
- if not c then c
- else
- let mutable i = 0
- let mutable res = true
- while i < lenx do
- let c = feq (get x i) (get y i)
- if not c then (res <- false; i <- lenx)
- else i <- i + 1
- res
-
- /// optimized case: Core implementation of structural equality on arrays.
- let GenericEqualityCharArray (x:char[]) (y:char[]) : bool=
- let lenx = x.Length
- let leny = y.Length
- let c = (lenx = leny)
- if not c then c
- else
- let mutable i = 0
- let mutable res = true
- while i < lenx do
- let c = charEq (get x i) (get y i)
- if not c then (res <- false; i <- lenx)
- else i <- i + 1
- res
-
- /// optimized case: Core implementation of structural equality on arrays.
- let GenericEqualityInt64Array (x:int64[]) (y:int64[]) : bool=
- let lenx = x.Length
- let leny = y.Length
- let c = (lenx = leny)
- if not c then c
- else
- let mutable i = 0
- let mutable res = true
- while i < lenx do
- let c = int64Eq (get x i) (get y i)
- if not c then (res <- false; i <- lenx)
- else i <- i + 1
- res
+ let inline ArrayEqualityWithERFlag<'T> (er:bool) (f:'T->'T->bool) (x:'T[]) (y:'T[]) : bool =
+ if er
+ then ArrayEquality (fun x y -> (not (f x x) && not (f y y)) || (f x y)) x y
+ else ArrayEquality f x y
+ let GenericEqualityByteArray x y = ArrayEquality byteEq x y
+ let GenericEqualityInt32Array x y = ArrayEquality int32Eq x y
+ let GenericEqualitySingleArray er x y = ArrayEqualityWithERFlag er float32Eq x y
+ let GenericEqualityDoubleArray er x y = ArrayEqualityWithERFlag er floatEq x y
+ let GenericEqualityCharArray x y = ArrayEquality charEq x y
+ let GenericEqualityInt64Array x y = ArrayEquality int64Eq x y
/// The core implementation of generic equality between two objects. This corresponds
@@ -1319,8 +1854,8 @@ namespace Microsoft.FSharp.Core
// Run in either PER or ER mode. In PER mode, equality involving a NaN returns "false".
// In ER mode, equality on two NaNs returns "true".
//
- // If "er" is true the "iec" is fsEqualityComparerNoHashingER
- // If "er" is false the "iec" is fsEqualityComparerNoHashingPER
+ // If "er" is true the "iec" is fsEqualityComparerUnlimitedHashingER
+ // If "er" is false the "iec" is fsEqualityComparerUnlimitedHashingPER
let rec GenericEqualityObj (er:bool) (iec:System.Collections.IEqualityComparer) ((xobj:obj),(yobj:obj)) : bool =
(*if objEq xobj yobj then true else *)
match xobj,yobj with
@@ -1330,10 +1865,12 @@ namespace Microsoft.FSharp.Core
| (:? string as xs),(:? string as ys) -> System.String.Equals(xs,ys)
// Permit structural equality on arrays
| (:? System.Array as arr1),_ ->
+ // due to the rules of the CLI type system, array casts are "assignment compatible"
+ // see: https://blogs.msdn.microsoft.com/ericlippert/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent/
+ // this means that the cast and comparison for byte will also handle sbyte, int32 handle uint32,
+ // and int64 handle uint64. Equality will still be correct.
match arr1,yobj with
- // Fast path
| (:? (obj[]) as arr1), (:? (obj[]) as arr2) -> GenericEqualityObjArray er iec arr1 arr2
- // Fast path
| (:? (byte[]) as arr1), (:? (byte[]) as arr2) -> GenericEqualityByteArray arr1 arr2
| (:? (int32[]) as arr1), (:? (int32[]) as arr2) -> GenericEqualityInt32Array arr1 arr2
| (:? (int64[]) as arr1), (:? (int64[]) as arr2) -> GenericEqualityInt64Array arr1 arr2
@@ -1410,42 +1947,155 @@ namespace Microsoft.FSharp.Core
check 0
/// optimized case: Core implementation of structural equality on object arrays.
- and GenericEqualityObjArray er iec (x:obj[]) (y:obj[]) : bool =
- let lenx = x.Length
- let leny = y.Length
- let c = (lenx = leny )
- if not c then c
- else
- let mutable i = 0
- let mutable res = true
- while i < lenx do
- let c = GenericEqualityObj er iec ((get x i),(get y i))
- if not c then (res <- false; i <- lenx)
- else i <- i + 1
- res
-
-
- /// One of the two unique instances of System.Collections.IEqualityComparer. Implements PER semantics
- /// where equality on NaN returns "false".
- let fsEqualityComparerNoHashingPER =
- { new System.Collections.IEqualityComparer with
- override iec.Equals(x:obj,y:obj) = GenericEqualityObj false iec (x,y) // PER Semantics
- override iec.GetHashCode(x:obj) = raise (InvalidOperationException (SR.GetString(SR.notUsedForHashing))) }
-
- /// One of the two unique instances of System.Collections.IEqualityComparer. Implements ER semantics
- /// where equality on NaN returns "true".
- let fsEqualityComparerNoHashingER =
- { new System.Collections.IEqualityComparer with
- override iec.Equals(x:obj,y:obj) = GenericEqualityObj true iec (x,y) // ER Semantics
- override iec.GetHashCode(x:obj) = raise (InvalidOperationException (SR.GetString(SR.notUsedForHashing))) }
+ and GenericEqualityObjArray er iec (xarray:obj[]) (yarray:obj[]) : bool =
+ ArrayEquality (fun x y -> GenericEqualityObj er iec (x, y)) xarray yarray
+
+ let isStructuralEquatable (ty:Type) = typeof.IsAssignableFrom ty
+ let isValueTypeStructuralEquatable (ty:Type) = isStructuralEquatable ty && ty.IsValueType
+
+ let canUseDefaultEqualityComparer er (rootType:Type) =
+ // "Default" equality for strings is by ordinal, so needs special handling required
+ canUseDotnetDefaultComparisonOrEquality Reflection.hasCustomEquality isStructuralEquatable false er rootType
+
+ let tryGetFSharpEqualityComparer (er:bool) (ty:Type) : obj =
+ match er, ty with
+ | false, ty when ty.Equals typeof ->
+ box { new EqualityComparer() with
+ member __.Equals (x,y) = (# "ceq" x y : bool #)
+ member __.GetHashCode x = x.GetHashCode () }
+ | false, ty when ty.Equals typeof ->
+ box { new EqualityComparer() with
+ member __.Equals (x,y) = (# "ceq" x y : bool #)
+ member __.GetHashCode x = x.GetHashCode () }
+ | true, ty when ty.Equals typeof -> box EqualityComparer.Default
+ | true, ty when ty.Equals typeof -> box EqualityComparer.Default
+ | _ -> null
+
+ let inline nullableEqualityComparer<'a when 'a : null> equals getHashCode =
+ box { new EqualityComparer<'a>() with
+ member __.Equals (x,y) =
+ match x, y with
+ | null, null -> true
+ | null, _ -> false
+ | _, null -> false
+ | _ -> equals x y
+
+ member __.GetHashCode x =
+ match x with
+ | null -> 0
+ | _ -> getHashCode x }
+
+ let inline castNullableEqualityComparer<'fromType, 'toType when 'toType : null and 'fromType : null> (equals:'toType->'toType->bool) (getHashCode:'toType->int) =
+ let castEquals (lhs:'fromType) (rhs:'fromType) = equals (unboxPrim lhs) (unboxPrim rhs)
+ let castGetHashCode (o:'fromType) = getHashCode (unboxPrim o)
+ nullableEqualityComparer castEquals castGetHashCode
+
+ let tryGetFSharpArrayEqualityComparer (ty:Type) er comparer : obj =
+ // the casts here between byte+sbyte, int32+uint32 and int64+uint64 are here to replicate the behaviour
+ // in GenericHashParamObj
+ if ty.Equals typeof then nullableEqualityComparer (fun x y -> GenericEqualityObjArray er comparer x y) (GenericHashObjArray fsEqualityComparerUnlimitedHashingPER)
+ elif ty.Equals typeof then nullableEqualityComparer GenericEqualityByteArray GenericHashByteArray
+ elif ty.Equals typeof then castNullableEqualityComparer GenericEqualityByteArray GenericHashByteArray
+ elif ty.Equals typeof then nullableEqualityComparer GenericEqualityInt32Array GenericHashInt32Array
+ elif ty.Equals typeof then castNullableEqualityComparer GenericEqualityInt32Array GenericHashInt32Array
+ elif ty.Equals typeof then nullableEqualityComparer GenericEqualityInt64Array GenericHashInt64Array
+ elif ty.Equals typeof then castNullableEqualityComparer GenericEqualityInt64Array GenericHashInt64Array
+ else null
+
+ let arrayEqualityComparer<'T> er comparer =
+ match tryGetFSharpArrayEqualityComparer typeof<'T> er comparer with
+ | :? EqualityComparer<'T> as arrayComparer -> arrayComparer
+ | _ ->
+ { new EqualityComparer<'T>() with
+ member __.Equals (x, y) =
+ let xobj, yobj = box x, box y
+ match xobj,yobj with
+ | null, null -> true
+ | null, _ -> false
+ | _, null -> false
+ | (:? (char[]) as arr1), (:? (char[]) as arr2) -> GenericEqualityCharArray arr1 arr2
+ | _ ->
+ match xobj,yobj with
+ | (:? (float32[]) as arr1), (:? (float32[]) as arr2) -> GenericEqualitySingleArray er arr1 arr2
+ | _ ->
+ match xobj,yobj with
+ | (:? (float[]) as arr1), (:? (float[])as arr2) -> GenericEqualityDoubleArray er arr1 arr2
+ | _ ->
+ match xobj,yobj with
+ | (:? System.Array as arr1), (:? System.Array as arr2) -> GenericEqualityArbArray er comparer arr1 arr2
+ | _ -> raise (Exception "invalid logic - expected array")
+
+ member __.GetHashCode x =
+ match box x with
+ | null -> 0
+ | :? System.Array as a -> GenericHashArbArray fsEqualityComparerUnlimitedHashingPER a
+ | _ -> raise (Exception "invalid logic - expected array") }
+
+ let structuralEqualityComparer<'T> comparer =
+ { new EqualityComparer<'T>() with
+ member __.Equals (x,y) =
+ match box x, box y with
+ | null, null -> true
+ | null, _ -> false
+ | _, null -> false
+ | (:? IStructuralEquatable as x1), yobj -> x1.Equals (yobj, comparer)
+ | _ -> raise (Exception "invalid logic - expected IStructuralEquatable")
+
+ member __.GetHashCode x =
+ match box x with
+ | null -> 0
+ | :? IStructuralEquatable as a -> a.GetHashCode fsEqualityComparerUnlimitedHashingPER
+ | _ -> raise (Exception "invalid logic - expected IStructuralEquatable") }
+
+ let structuralEqualityComparerValueType<'T> comparer =
+ { new EqualityComparer<'T>() with
+ member __.Equals (x,y) = ((box x):?>IStructuralEquatable).Equals (y, comparer)
+ member __.GetHashCode x = ((box x):?>IStructuralEquatable).GetHashCode fsEqualityComparerUnlimitedHashingPER }
+
+ let unknownEqualityComparer<'T> er comparer =
+ { new EqualityComparer<'T>() with
+ member __.Equals (x,y) = GenericEqualityObj er comparer (box x, box y)
+ member __.GetHashCode x = GenericHashParamObj fsEqualityComparerUnlimitedHashingPER (box x) }
+
+ let getGenericEquality<'T> er =
+ match tryGetFSharpEqualityComparer er typeof<'T> with
+ | :? EqualityComparer<'T> as call -> call
+ | _ when canUseDefaultEqualityComparer er typeof<'T> -> EqualityComparer<'T>.Default
+ | _ when isArray typeof<'T> && er -> arrayEqualityComparer true fsEqualityComparerUnlimitedHashingER
+ | _ when isArray typeof<'T> -> arrayEqualityComparer false fsEqualityComparerUnlimitedHashingPER
+ | _ when isValueTypeStructuralEquatable typeof<'T> && er -> structuralEqualityComparerValueType fsEqualityComparerUnlimitedHashingER
+ | _ when isValueTypeStructuralEquatable typeof<'T> -> structuralEqualityComparerValueType fsEqualityComparerUnlimitedHashingPER
+ | _ when isStructuralEquatable typeof<'T> && er -> structuralEqualityComparer fsEqualityComparerUnlimitedHashingER
+ | _ when isStructuralEquatable typeof<'T> -> structuralEqualityComparer fsEqualityComparerUnlimitedHashingPER
+ | _ when er -> unknownEqualityComparer true fsEqualityComparerUnlimitedHashingER
+ | _ -> unknownEqualityComparer false fsEqualityComparerUnlimitedHashingPER
+
+ []
+ type FSharpEqualityComparer_ER<'T> private () =
+ static let comparer = getGenericEquality<'T> true
+ static member EqualityComparer = comparer
+
+ []
+ type FSharpEqualityComparer_PER<'T> private () =
+ static let comparer = getGenericEquality<'T> false
+ static member EqualityComparer = comparer
+
+ let inline FSharpEqualityComparer_ER_Equals (x:'T) (y:'T) =
+ FSharpEqualityComparer_ER<'T>.EqualityComparer.Equals (x, y)
+
+ let inline FSharpEqualityComparer_PER_Equals (x:'T) (y:'T) =
+ FSharpEqualityComparer_PER<'T>.EqualityComparer.Equals (x, y)
+
+ let inline FSharpEqualityComparer_GetHashCode (x:'T) =
+ FSharpEqualityComparer_PER<'T>.EqualityComparer.GetHashCode x
/// Implements generic equality between two values, with PER semantics for NaN (so equality on two NaN values returns false)
//
// The compiler optimizer is aware of this function (see use of generic_equality_per_inner_vref in opt.fs)
// and devirtualizes calls to it based on "T".
let GenericEqualityIntrinsic (x : 'T) (y : 'T) : bool =
- GenericEqualityObj false fsEqualityComparerNoHashingPER ((box x), (box y))
-
+ FSharpEqualityComparer_PER<'T>.EqualityComparer.Equals (x, y)
+
/// Implements generic equality between two values, with ER semantics for NaN (so equality on two NaN values returns true)
//
// ER semantics is used for recursive calls when implementing .Equals(that) for structural data, see the code generated for record and union types in augment.fs
@@ -1453,16 +2103,20 @@ namespace Microsoft.FSharp.Core
// The compiler optimizer is aware of this function (see use of generic_equality_er_inner_vref in opt.fs)
// and devirtualizes calls to it based on "T".
let GenericEqualityERIntrinsic (x : 'T) (y : 'T) : bool =
- GenericEqualityObj true fsEqualityComparerNoHashingER ((box x), (box y))
+ FSharpEqualityComparer_ER<'T>.EqualityComparer.Equals (x, y)
/// Implements generic equality between two values using "comp" for recursive calls.
//
// The compiler optimizer is aware of this function (see use of generic_equality_withc_inner_vref in opt.fs)
// and devirtualizes calls to it based on "T", and under the assumption that "comp"
- // is either fsEqualityComparerNoHashingER or fsEqualityComparerNoHashingPER.
+ // is either fsEqualityComparerUnlimitedHashingER or fsEqualityComparerUnlimitedHashingPER.
let GenericEqualityWithComparerIntrinsic (comp : System.Collections.IEqualityComparer) (x : 'T) (y : 'T) : bool =
- comp.Equals((box x),(box y))
-
+ if obj.ReferenceEquals (comp, fsEqualityComparerUnlimitedHashingPER) then
+ FSharpEqualityComparer_PER<'T>.EqualityComparer.Equals (x, y)
+ elif obj.ReferenceEquals (comp, fsEqualityComparerUnlimitedHashingER) then
+ FSharpEqualityComparer_ER<'T>.EqualityComparer.Equals (x, y)
+ else
+ comp.Equals (box x, box y)
/// Implements generic equality between two values, with ER semantics for NaN (so equality on two NaN values returns true)
//
@@ -1553,125 +2207,6 @@ namespace Microsoft.FSharp.Core
let inline GenericInequalityERFast (x:'T) (y:'T) = (not(GenericEqualityERFast x y) : bool)
- //-------------------------------------------------------------------------
- // LanguagePrimitives.HashCompare: HASHING.
- //-------------------------------------------------------------------------
-
-
-
- let defaultHashNodes = 18
-
- /// The implementation of IEqualityComparer, using depth-limited for hashing and PER semantics for NaN equality.
- type CountLimitedHasherPER(sz:int) =
- []
- val mutable nodeCount : int
-
- member x.Fresh() =
- if (System.Threading.Interlocked.CompareExchange(&(x.nodeCount), sz, 0) = 0) then
- x
- else
- new CountLimitedHasherPER(sz)
-
- interface IEqualityComparer
-
- /// The implementation of IEqualityComparer, using unlimited depth for hashing and ER semantics for NaN equality.
- type UnlimitedHasherER() =
- interface IEqualityComparer
-
- /// The implementation of IEqualityComparer, using unlimited depth for hashing and PER semantics for NaN equality.
- type UnlimitedHasherPER() =
- interface IEqualityComparer
-
-
- /// The unique object for unlimited depth for hashing and ER semantics for equality.
- let fsEqualityComparerUnlimitedHashingER = UnlimitedHasherER()
-
- /// The unique object for unlimited depth for hashing and PER semantics for equality.
- let fsEqualityComparerUnlimitedHashingPER = UnlimitedHasherPER()
-
- let inline HashCombine nr x y = (x <<< 1) + y + 631 * nr
-
- let GenericHashObjArray (iec : System.Collections.IEqualityComparer) (x: obj[]) : int =
- let len = x.Length
- let mutable i = len - 1
- if i > defaultHashNodes then i <- defaultHashNodes // limit the hash
- let mutable acc = 0
- while (i >= 0) do
- // NOTE: GenericHash* call decreases nr
- acc <- HashCombine i acc (iec.GetHashCode(x.GetValue(i)));
- i <- i - 1
- acc
-
- // optimized case - byte arrays
- let GenericHashByteArray (x: byte[]) : int =
- let len = length x
- let mutable i = len - 1
- if i > defaultHashNodes then i <- defaultHashNodes // limit the hash
- let mutable acc = 0
- while (i >= 0) do
- acc <- HashCombine i acc (intOfByte (get x i));
- i <- i - 1
- acc
-
- // optimized case - int arrays
- let GenericHashInt32Array (x: int[]) : int =
- let len = length x
- let mutable i = len - 1
- if i > defaultHashNodes then i <- defaultHashNodes // limit the hash
- let mutable acc = 0
- while (i >= 0) do
- acc <- HashCombine i acc (get x i);
- i <- i - 1
- acc
-
- // optimized case - int arrays
- let GenericHashInt64Array (x: int64[]) : int =
- let len = length x
- let mutable i = len - 1
- if i > defaultHashNodes then i <- defaultHashNodes // limit the hash
- let mutable acc = 0
- while (i >= 0) do
- acc <- HashCombine i acc (int32 (get x i));
- i <- i - 1
- acc
-
- // special case - arrays do not by default have a decent structural hashing function
- let GenericHashArbArray (iec : System.Collections.IEqualityComparer) (x: System.Array) : int =
- match x.Rank with
- | 1 ->
- let b = x.GetLowerBound(0)
- let len = x.Length
- let mutable i = b + len - 1
- if i > b + defaultHashNodes then i <- b + defaultHashNodes // limit the hash
- let mutable acc = 0
- while (i >= b) do
- // NOTE: GenericHash* call decreases nr
- acc <- HashCombine i acc (iec.GetHashCode(x.GetValue(i)));
- i <- i - 1
- acc
- | _ ->
- HashCombine 10 (x.GetLength(0)) (x.GetLength(1))
-
- // Core implementation of structural hashing, corresponds to pseudo-code in the
- // F# Language spec. Searches for the IStructuralHash interface, otherwise uses GetHashCode().
- // Arrays are structurally hashed through a separate technique.
- //
- // "iec" is either fsEqualityComparerUnlimitedHashingER, fsEqualityComparerUnlimitedHashingPER or a CountLimitedHasherPER.
- let rec GenericHashParamObj (iec : System.Collections.IEqualityComparer) (x: obj) : int =
- match x with
- | null -> 0
- | (:? System.Array as a) ->
- match a with
- | :? (obj[]) as oa -> GenericHashObjArray iec oa
- | :? (byte[]) as ba -> GenericHashByteArray ba
- | :? (int[]) as ba -> GenericHashInt32Array ba
- | :? (int64[]) as ba -> GenericHashInt64Array ba
- | _ -> GenericHashArbArray iec a
- | :? IStructuralEquatable as a ->
- a.GetHashCode(iec)
- | _ ->
- x.GetHashCode()
-
/// Fill in the implementation of CountLimitedHasherPER
type CountLimitedHasherPER with
@@ -1703,10 +2238,12 @@ namespace Microsoft.FSharp.Core
//
// NOTE: The compiler optimizer is aware of this function (see uses of generic_hash_inner_vref in opt.fs)
// and devirtualizes calls to it based on type "T".
- let GenericHashIntrinsic input = GenericHashParamObj fsEqualityComparerUnlimitedHashingPER (box input)
+ let GenericHashIntrinsic input =
+ FSharpEqualityComparer_PER<'T>.EqualityComparer.GetHashCode input
/// Intrinsic for calls to depth-limited structural hashing that were not optimized by static conditionals.
- let LimitedGenericHashIntrinsic limit input = GenericHashParamObj (CountLimitedHasherPER(limit)) (box input)
+ let LimitedGenericHashIntrinsic limit input =
+ GenericHashParamObj (CountLimitedHasherPER(limit)) (box input)
/// Intrinsic for a recursive call to structural hashing that was not optimized by static conditionals.
//
@@ -1716,22 +2253,11 @@ namespace Microsoft.FSharp.Core
// NOTE: The compiler optimizer is aware of this function (see uses of generic_hash_withc_inner_vref in opt.fs)
// and devirtualizes calls to it based on type "T".
let GenericHashWithComparerIntrinsic<'T> (comp : System.Collections.IEqualityComparer) (input : 'T) : int =
- GenericHashParamObj comp (box input)
+ if obj.ReferenceEquals (comp, fsEqualityComparerUnlimitedHashingPER) then
+ FSharpEqualityComparer_PER<'T>.EqualityComparer.GetHashCode input
+ else
+ GenericHashParamObj comp (box input)
- let inline HashString (s:string) =
- match s with
- | null -> 0
- | _ -> s.GetHashCode()
-
- // from mscorlib v4.0.30319
- let inline HashChar (x:char) = (# "or" (# "shl" x 16 : int #) x : int #)
- let inline HashSByte (x:sbyte) = (# "xor" (# "shl" x 8 : int #) x : int #)
- let inline HashInt16 (x:int16) = (# "or" (# "conv.u2" x : int #) (# "shl" x 16 : int #) : int #)
- let inline HashInt64 (x:int64) = (# "xor" (# "conv.i4" x : int #) (# "conv.i4" (# "shr" x 32 : int #) : int #) : int #)
- let inline HashUInt64 (x:uint64) = (# "xor" (# "conv.i4" x : int #) (# "conv.i4" (# "shr.un" x 32 : int #) : int #) : int #)
- let inline HashIntPtr (x:nativeint) = (# "conv.i4" (# "conv.u8" x : uint64 #) : int #)
- let inline HashUIntPtr (x:unativeint) = (# "and" (# "conv.i4" (# "conv.u8" x : uint64 #) : int #) 0x7fffffff : int #)
-
/// Core entry into structural hashing for either limited or unlimited hashing.
//
// "iec" is assumed to be either fsEqualityComparerUnlimitedHashingER, fsEqualityComparerUnlimitedHashingPER or
@@ -2029,47 +2555,8 @@ namespace Microsoft.FSharp.Core
member self.GetHashCode(x) = GenericLimitedHash limit x
member self.Equals(x,y) = GenericEquality x y }
- let BoolIEquality = MakeGenericEqualityComparer()
- let CharIEquality = MakeGenericEqualityComparer()
- let StringIEquality = MakeGenericEqualityComparer()
- let SByteIEquality = MakeGenericEqualityComparer()
- let Int16IEquality = MakeGenericEqualityComparer()
- let Int32IEquality = MakeGenericEqualityComparer()
- let Int64IEquality = MakeGenericEqualityComparer()
- let IntPtrIEquality = MakeGenericEqualityComparer()
- let ByteIEquality = MakeGenericEqualityComparer()
- let UInt16IEquality = MakeGenericEqualityComparer()
- let UInt32IEquality = MakeGenericEqualityComparer()
- let UInt64IEquality = MakeGenericEqualityComparer()
- let UIntPtrIEquality = MakeGenericEqualityComparer()
- let FloatIEquality = MakeGenericEqualityComparer()
- let Float32IEquality = MakeGenericEqualityComparer()
- let DecimalIEquality = MakeGenericEqualityComparer()
-
- []
- type FastGenericEqualityComparerTable<'T>() =
- static let f : System.Collections.Generic.IEqualityComparer<'T> =
- match typeof<'T> with
- | ty when ty.Equals(typeof) -> unboxPrim (box BoolIEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box ByteIEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box Int32IEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box UInt32IEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box CharIEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box SByteIEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box Int16IEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box Int64IEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box IntPtrIEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box UInt16IEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box UInt64IEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box UIntPtrIEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box FloatIEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box Float32IEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box DecimalIEquality)
- | ty when ty.Equals(typeof) -> unboxPrim (box StringIEquality)
- | _ -> MakeGenericEqualityComparer<'T>()
- static member Function : System.Collections.Generic.IEqualityComparer<'T> = f
-
- let FastGenericEqualityComparerFromTable<'T> = FastGenericEqualityComparerTable<'T>.Function
+ let FastGenericEqualityComparerFromTable<'T> =
+ HashCompare.FSharpEqualityComparer_PER<'T>.EqualityComparer :> IEqualityComparer<'T>
// This is the implementation of HashIdentity.Structural. In most cases this just becomes
// FastGenericEqualityComparerFromTable.
@@ -2101,99 +2588,15 @@ namespace Microsoft.FSharp.Core
// MakeGenericEqualityComparer. This is then reduced by further inlining to the primitives
// known to the F# compiler which are then often optimized for the particular nominal type involved.
when 'T : 'T = MakeGenericEqualityComparer<'T>()
-
+
let inline FastLimitedGenericEqualityComparer<'T>(limit) = MakeGenericLimitedEqualityComparer<'T>(limit)
let inline MakeGenericComparer<'T>() =
{ new System.Collections.Generic.IComparer<'T> with
member __.Compare(x,y) = GenericComparison x y }
-
- let CharComparer = MakeGenericComparer()
- let StringComparer = MakeGenericComparer()
- let SByteComparer = MakeGenericComparer()
- let Int16Comparer = MakeGenericComparer()
- let Int32Comparer = MakeGenericComparer()
- let Int64Comparer = MakeGenericComparer()
- let IntPtrComparer = MakeGenericComparer()
- let ByteComparer = MakeGenericComparer()
- let UInt16Comparer = MakeGenericComparer()
- let UInt32Comparer = MakeGenericComparer()
- let UInt64Comparer = MakeGenericComparer()
- let UIntPtrComparer = MakeGenericComparer()
- let FloatComparer = MakeGenericComparer()
- let Float32Comparer = MakeGenericComparer()
- let DecimalComparer = MakeGenericComparer()
- let BoolComparer = MakeGenericComparer()
-
- /// Use a type-indexed table to ensure we only create a single FastStructuralComparison function
- /// for each type
- []
- type FastGenericComparerTable<'T>() =
-
- // The CLI implementation of mscorlib optimizes array sorting
- // when the comparer is either null or precisely
- // reference-equals to System.Collections.Generic.Comparer<'T>.Default.
- // This is an indication that a "fast" array sorting helper can be used.
- //
- // So, for all the types listed below, we want to pass in a value of "null" for
- // the comparer object. Note that F# generic comparison coincides precisely with
- // System.Collections.Generic.Comparer<'T>.Default for these types.
- //
- // A "null" comparer is only valid if the values do not have identity, e.g. integers.
- // That is, an unstable sort of the array must be the semantically the
- // same as a stable sort of the array. See Array.stableSortInPlace.
- //
- // REVIEW: in a future version we could extend this to include additional types
- static let fCanBeNull : System.Collections.Generic.IComparer<'T> =
- match typeof<'T> with
- | ty when ty.Equals(typeof) -> unboxPrim (box IntPtrComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box UIntPtrComparer)
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> null
- | ty when ty.Equals(typeof) -> unboxPrim (box StringComparer)
- | ty when ty.Equals(typeof) -> null
- | _ -> MakeGenericComparer<'T>()
-
- static let f : System.Collections.Generic.IComparer<'T> =
- match typeof<'T> with
- | ty when ty.Equals(typeof) -> unboxPrim (box ByteComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box CharComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box SByteComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box Int16Comparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box Int32Comparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box Int64Comparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box IntPtrComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box UInt16Comparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box UInt32Comparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box UInt64Comparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box UIntPtrComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box FloatComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box Float32Comparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box DecimalComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box StringComparer)
- | ty when ty.Equals(typeof) -> unboxPrim (box BoolComparer)
- | _ ->
- // Review: There are situations where we should be able
- // to return System.Collections.Generic.Comparer<'T>.Default here.
- // For example, for any value type.
- MakeGenericComparer<'T>()
-
- static member Value : System.Collections.Generic.IComparer<'T> = f
-
- static member ValueCanBeNullIfDefaultSemantics : System.Collections.Generic.IComparer<'T> = fCanBeNull
- let FastGenericComparerFromTable<'T> =
- FastGenericComparerTable<'T>.Value
+ let FastGenericComparerFromTable<'T> : IComparer<'T> =
+ HashCompare.FSharpComparer_ER<'T>.Comparer :> IComparer<'T>
let inline FastGenericComparer<'T> =
// This gets used is 'T can't be resolved to anything interesting
@@ -2224,7 +2627,10 @@ namespace Microsoft.FSharp.Core
// which are then optimized for the particular nominal type involved.
when 'T : 'T = MakeGenericComparer<'T>()
- let FastGenericComparerCanBeNull<'T> = FastGenericComparerTable<'T>.ValueCanBeNullIfDefaultSemantics
+ let FastGenericComparerInternal<'T> : Comparer<'T> =
+ HashCompare.FSharpComparer_InternalUse_ER<'T>.Comparer
+ let EquivalentForStableAndUnstableSort<'T> : bool =
+ HashCompare.FSharpComparer_InternalUse_ER<'T>.EquivalentForStableAndUnstableSort
//-------------------------------------------------------------------------
// LanguagePrimitives: ENUMS
diff --git a/src/fsharp/FSharp.Core/prim-types.fsi b/src/fsharp/FSharp.Core/prim-types.fsi
index 3c35192188e..d68fee67659 100644
--- a/src/fsharp/FSharp.Core/prim-types.fsi
+++ b/src/fsharp/FSharp.Core/prim-types.fsi
@@ -959,7 +959,10 @@ namespace Microsoft.FSharp.Core
val inline FastGenericComparer<'T> : System.Collections.Generic.IComparer<'T> when 'T : comparison
/// Make an F# comparer object for the given type, where it can be null if System.Collections.Generic.Comparer<'T>.Default
- val internal FastGenericComparerCanBeNull<'T> : System.Collections.Generic.IComparer<'T> when 'T : comparison
+ val internal FastGenericComparerInternal<'T> : System.Collections.Generic.Comparer<'T> when 'T : comparison
+
+ /// As an optimization, determine if a fast unstable sort can be used with equivalent results
+ val internal EquivalentForStableAndUnstableSort<'T> : bool
/// Make an F# hash/equality object for the given type
val inline FastGenericEqualityComparer<'T> : System.Collections.Generic.IEqualityComparer<'T> when 'T : equality
@@ -1336,8 +1339,38 @@ namespace Microsoft.FSharp.Core
//[]
val inline SetArray4D : target:'T[,,,] -> index1:int -> index2:int -> index3:int -> index4:int -> value:'T -> unit
+ module internal Reflection =
+ val internal tupleNames : string []
+ val internal isTupleType : Type -> bool
+ val internal tryFindSourceConstructFlagsOfType : Type * byref -> bool
+ val internal fieldPropsOfRecordType : Type * System.Reflection.BindingFlags -> System.Reflection.PropertyInfo[]
+ val internal isRecordType : Type * System.Reflection.BindingFlags -> bool
+ val internal getUnionTypeTagNameMap : Type * System.Reflection.BindingFlags -> (int*string)[]
+ val internal fieldsPropsOfUnionCase : Type * int* System.Reflection.BindingFlags -> System.Reflection.PropertyInfo[]
+ val internal isUnionType : Type * System.Reflection.BindingFlags -> bool
+
/// The F# compiler emits calls to some of the functions in this module as part of the compiled form of some language constructs
module HashCompare =
+ []
+ type FSharpEqualityComparer_ER<'T> =
+ static member EqualityComparer : System.Collections.Generic.EqualityComparer<'T>
+
+ []
+ type FSharpEqualityComparer_PER<'T> =
+ static member EqualityComparer : System.Collections.Generic.EqualityComparer<'T>
+
+ /// A primitive entry point used by the F# compiler for optimization purposes.
+ []
+ val inline FSharpEqualityComparer_ER_Equals : x:'T -> y:'T -> bool
+
+ /// A primitive entry point used by the F# compiler for optimization purposes.
+ []
+ val inline FSharpEqualityComparer_PER_Equals : x:'T -> y:'T -> bool
+
+ /// A primitive entry point used by the F# compiler for optimization purposes.
+ []
+ val inline FSharpEqualityComparer_GetHashCode : x:'T -> int
+
/// A primitive entry point used by the F# compiler for optimization purposes.
[]
val PhysicalHashIntrinsic : input:'T -> int when 'T : not struct
diff --git a/src/fsharp/FSharp.Core/reflect.fs b/src/fsharp/FSharp.Core/reflect.fs
index 3f7c459be64..4a767cc6a83 100644
--- a/src/fsharp/FSharp.Core/reflect.fs
+++ b/src/fsharp/FSharp.Core/reflect.fs
@@ -66,145 +66,16 @@ module internal Impl =
//-----------------------------------------------------------------
// ATTRIBUTE DECOMPILATION
- let tryFindCompilationMappingAttribute (attrs: obj[]) =
- match attrs with
- | null | [| |] -> None
- | [| res |] -> let a = (res :?> CompilationMappingAttribute) in Some (a.SourceConstructFlags, a.SequenceNumber, a.VariantNumber)
- | _ -> invalidOp (SR.GetString (SR.multipleCompilationMappings))
-
- let findCompilationMappingAttribute (attrs: obj[]) =
- match tryFindCompilationMappingAttribute attrs with
- | None -> failwith "no compilation mapping attribute"
- | Some a -> a
-
- let cmaName = typeof.FullName
- let assemblyName = typeof.Assembly.GetName().Name
- let _ = assert (assemblyName = "FSharp.Core")
-
- let tryFindCompilationMappingAttributeFromData (attrs: IList) =
- match attrs with
- | null -> None
- | _ ->
- let mutable res = None
- for a in attrs do
- if a.Constructor.DeclaringType.FullName = cmaName then
- let args = a.ConstructorArguments
- let flags =
- match args.Count with
- | 1 -> ((let x = args.[0] in x.Value :?> SourceConstructFlags), 0, 0)
- | 2 -> ((let x = args.[0] in x.Value :?> SourceConstructFlags), (let x = args.[1] in x.Value :?> int), 0)
- | 3 -> ((let x = args.[0] in x.Value :?> SourceConstructFlags), (let x = args.[1] in x.Value :?> int), (let x = args.[2] in x.Value :?> int))
- | _ -> (enum 0, 0, 0)
- res <- Some flags
- res
-
- let findCompilationMappingAttributeFromData attrs =
- match tryFindCompilationMappingAttributeFromData attrs with
- | None -> failwith "no compilation mapping attribute"
- | Some a -> a
-
- let tryFindCompilationMappingAttributeFromType (typ: Type) =
- let assem = typ.Assembly
- if (not (isNull assem)) && assem.ReflectionOnly then
- tryFindCompilationMappingAttributeFromData ( typ.GetCustomAttributesData())
- else
- tryFindCompilationMappingAttribute ( typ.GetCustomAttributes (typeof, false))
-
- let tryFindCompilationMappingAttributeFromMemberInfo (info: MemberInfo) =
- let assem = info.DeclaringType.Assembly
- if (not (isNull assem)) && assem.ReflectionOnly then
- tryFindCompilationMappingAttributeFromData (info.GetCustomAttributesData())
- else
- tryFindCompilationMappingAttribute (info.GetCustomAttributes (typeof, false))
-
- let findCompilationMappingAttributeFromMemberInfo (info: MemberInfo) =
- let assem = info.DeclaringType.Assembly
- if (not (isNull assem)) && assem.ReflectionOnly then
- findCompilationMappingAttributeFromData (info.GetCustomAttributesData())
+ let tryFindSourceConstructFlagsOfType (typ:Type) =
+ let mutable res = Unchecked.defaultof<_>
+ if LanguagePrimitives.Reflection.tryFindSourceConstructFlagsOfType (typ, &res) then
+ Some res
else
- findCompilationMappingAttribute (info.GetCustomAttributes (typeof, false))
-
- let sequenceNumberOfMember (x: MemberInfo) = let (_, n, _) = findCompilationMappingAttributeFromMemberInfo x in n
- let variantNumberOfMember (x: MemberInfo) = let (_, _, vn) = findCompilationMappingAttributeFromMemberInfo x in vn
-
- let sortFreshArray f arr = Array.sortInPlaceWith f arr; arr
-
- let isFieldProperty (prop : PropertyInfo) =
- match tryFindCompilationMappingAttributeFromMemberInfo prop with
- | None -> false
- | Some (flags, _n, _vn) -> (flags &&& SourceConstructFlags.KindMask) = SourceConstructFlags.Field
-
- let tryFindSourceConstructFlagsOfType (typ: Type) =
- match tryFindCompilationMappingAttributeFromType typ with
- | None -> None
- | Some (flags, _n, _vn) -> Some flags
-
+ None
//-----------------------------------------------------------------
// UNION DECOMPILATION
- // Get the type where the type definitions are stored
- let getUnionCasesTyp (typ: Type, _bindingFlags) =
-#if CASES_IN_NESTED_CLASS
- let casesTyp = typ.GetNestedType("Cases", bindingFlags)
- if casesTyp.IsGenericTypeDefinition then casesTyp.MakeGenericType(typ.GetGenericArguments())
- else casesTyp
-#else
- typ
-#endif
-
- let getUnionTypeTagNameMap (typ: Type, bindingFlags) =
- let enumTyp = typ.GetNestedType("Tags", bindingFlags)
- // Unions with a singleton case do not get a Tags type (since there is only one tag), hence enumTyp may be null in this case
- match enumTyp with
- | null ->
- typ.GetMethods(staticMethodFlags ||| bindingFlags)
- |> Array.choose (fun minfo ->
- match tryFindCompilationMappingAttributeFromMemberInfo minfo with
- | None -> None
- | Some (flags, n, _vn) ->
- if (flags &&& SourceConstructFlags.KindMask) = SourceConstructFlags.UnionCase then
- let nm = minfo.Name
- // chop "get_" or "New" off the front
- let nm =
- if not (isListType typ) && not (isOptionType typ) then
- if nm.Length > 4 && nm.[0..3] = "get_" then nm.[4..]
- elif nm.Length > 3 && nm.[0..2] = "New" then nm.[3..]
- else nm
- else nm
- Some (n, nm)
- else
- None)
- | _ ->
- enumTyp.GetFields(staticFieldFlags ||| bindingFlags)
- |> Array.filter (fun (f: FieldInfo) -> f.IsStatic && f.IsLiteral)
- |> sortFreshArray (fun f1 f2 -> compare (f1.GetValue null :?> int) (f2.GetValue null :?> int))
- |> Array.map (fun tagfield -> (tagfield.GetValue null :?> int), tagfield.Name)
-
- let getUnionCaseTyp (typ: Type, tag: int, bindingFlags) =
- let tagFields = getUnionTypeTagNameMap(typ, bindingFlags)
- let tagField = tagFields |> Array.pick (fun (i, f) -> if i = tag then Some f else None)
- if tagFields.Length = 1 then
- typ
- else
- // special case: two-cased DU annotated with CompilationRepresentation(UseNullAsTrueValue)
- // in this case it will be compiled as one class: return self type for non-nullary case and null for nullary
- let isTwoCasedDU =
- if tagFields.Length = 2 then
- match typ.GetCustomAttributes(typeof, false) with
- | [|:? CompilationRepresentationAttribute as attr|] ->
- (attr.Flags &&& CompilationRepresentationFlags.UseNullAsTrueValue) = CompilationRepresentationFlags.UseNullAsTrueValue
- | _ -> false
- else
- false
- if isTwoCasedDU then
- typ
- else
- let casesTyp = getUnionCasesTyp (typ, bindingFlags)
- let caseTyp = casesTyp.GetNestedType(tagField, bindingFlags) // if this is null then the union is nullary
- match caseTyp with
- | null -> null
- | _ when caseTyp.IsGenericTypeDefinition -> caseTyp.MakeGenericType(casesTyp.GetGenericArguments())
- | _ -> caseTyp
+ let getUnionTypeTagNameMap (typ:Type,bindingFlags) = LanguagePrimitives.Reflection.getUnionTypeTagNameMap (typ, bindingFlags)
let getUnionTagConverter (typ: Type, bindingFlags) =
if isOptionType typ then (fun tag -> match tag with 0 -> "None" | 1 -> "Some" | _ -> invalidArg "tag" (SR.GetString (SR.outOfRange)))
@@ -214,17 +85,11 @@ module internal Impl =
(fun tag -> tagfieldmap.[tag])
let isUnionType (typ: Type, bindingFlags: BindingFlags) =
+ // isOptionType & isListType are not necessary. There were here before the code was refactored into prim-types
+ // presumably as an optimization, so have not been removed (no performance testing run at this time)
isOptionType typ ||
isListType typ ||
- match tryFindSourceConstructFlagsOfType typ with
- | None -> false
- | Some flags ->
- (flags &&& SourceConstructFlags.KindMask) = SourceConstructFlags.SumType &&
- // We see private representations only if BindingFlags.NonPublic is set
- (if (flags &&& SourceConstructFlags.NonPublicRepresentation) <> enum 0 then
- (bindingFlags &&& BindingFlags.NonPublic) <> enum 0
- else
- true)
+ LanguagePrimitives.Reflection.isUnionType (typ, bindingFlags)
// Check the base type - if it is also an F# type then
// for the moment we know it is a Discriminated Union
@@ -248,14 +113,7 @@ module internal Impl =
| 1 (* Cons *) -> getInstancePropertyInfos (typ, [| "Head"; "Tail" |], bindingFlags)
| _ -> failwith "fieldsPropsOfUnionCase"
else
- // Lookup the type holding the fields for the union case
- let caseTyp = getUnionCaseTyp (typ, tag, bindingFlags)
- let caseTyp = match caseTyp with null -> typ | _ -> caseTyp
- caseTyp.GetProperties(instancePropertyFlags ||| bindingFlags)
- |> Array.filter isFieldProperty
- |> Array.filter (fun prop -> variantNumberOfMember prop = tag)
- |> sortFreshArray (fun p1 p2 -> compare (sequenceNumberOfMember p1) (sequenceNumberOfMember p2))
-
+ LanguagePrimitives.Reflection.fieldsPropsOfUnionCase (typ, tag, bindingFlags)
let getUnionCaseRecordReader (typ: Type, tag: int, bindingFlags) =
let props = fieldsPropsOfUnionCase (typ, tag, bindingFlags)
@@ -310,54 +168,9 @@ module internal Impl =
//-----------------------------------------------------------------
// TUPLE DECOMPILATION
- let tupleNames =
- [| "System.Tuple`1"
- "System.Tuple`2"
- "System.Tuple`3"
- "System.Tuple`4"
- "System.Tuple`5"
- "System.Tuple`6"
- "System.Tuple`7"
- "System.Tuple`8"
- "System.Tuple"
- "System.ValueTuple`1"
- "System.ValueTuple`2"
- "System.ValueTuple`3"
- "System.ValueTuple`4"
- "System.ValueTuple`5"
- "System.ValueTuple`6"
- "System.ValueTuple`7"
- "System.ValueTuple`8"
- "System.ValueTuple" |]
-
- let simpleTupleNames =
- [| "Tuple`1"
- "Tuple`2"
- "Tuple`3"
- "Tuple`4"
- "Tuple`5"
- "Tuple`6"
- "Tuple`7"
- "Tuple`8"
- "ValueTuple`1"
- "ValueTuple`2"
- "ValueTuple`3"
- "ValueTuple`4"
- "ValueTuple`5"
- "ValueTuple`6"
- "ValueTuple`7"
- "ValueTuple`8" |]
-
- let isTupleType (typ: Type) =
- // We need to be careful that we only rely typ.IsGenericType, typ.Namespace and typ.Name here.
- //
- // Historically the FSharp.Core reflection utilities get used on implementations of
- // System.Type that don't have functionality such as .IsEnum and .FullName fully implemented.
- // This happens particularly over TypeBuilderInstantiation types in the ProvideTypes implementation of System.Type
- // used in F# type providers.
- typ.IsGenericType &&
- typ.Namespace = "System" &&
- simpleTupleNames |> Seq.exists typ.Name.StartsWith
+ let tupleNames = LanguagePrimitives.Reflection.tupleNames
+
+ let isTupleType (typ:Type) = LanguagePrimitives.Reflection.isTupleType typ
let maxTuple = 8
// Which field holds the nested tuple?
@@ -565,21 +378,9 @@ module internal Impl =
isFunctionType typ ||
(match typ.BaseType with null -> false | bty -> isClosureRepr bty)
- let isRecordType (typ: Type, bindingFlags: BindingFlags) =
- match tryFindSourceConstructFlagsOfType typ with
- | None -> false
- | Some flags ->
- (flags &&& SourceConstructFlags.KindMask) = SourceConstructFlags.RecordType &&
- // We see private representations only if BindingFlags.NonPublic is set
- (if (flags &&& SourceConstructFlags.NonPublicRepresentation) <> enum 0 then
- (bindingFlags &&& BindingFlags.NonPublic) <> enum 0
- else
- true)
-
- let fieldPropsOfRecordType(typ: Type, bindingFlags) =
- typ.GetProperties(instancePropertyFlags ||| bindingFlags)
- |> Array.filter isFieldProperty
- |> sortFreshArray (fun p1 p2 -> compare (sequenceNumberOfMember p1) (sequenceNumberOfMember p2))
+ let isRecordType (typ:Type,bindingFlags:BindingFlags) = LanguagePrimitives.Reflection.isRecordType (typ, bindingFlags)
+
+ let fieldPropsOfRecordType (typ:Type, bindingFlags) = LanguagePrimitives.Reflection.fieldPropsOfRecordType (typ, bindingFlags)
let getRecordReader(typ: Type, bindingFlags) =
let props = fieldPropsOfRecordType(typ, bindingFlags)
diff --git a/src/fsharp/Optimizer.fs b/src/fsharp/Optimizer.fs
index 1c8e4928b0d..f87751621c4 100644
--- a/src/fsharp/Optimizer.fs
+++ b/src/fsharp/Optimizer.fs
@@ -2618,11 +2618,15 @@ and TryDevirtualizeApplication cenv env (f, tyargs, args, m) =
// REVIEW: GenericEqualityIntrinsic (which has no comparer) implements PER semantics (5537: this should be ER semantics)
// We are devirtualizing to a Equals(T) method which also implements PER semantics (5537: this should be ER semantics)
| Expr.Val (v, _, _), [ty], _ when CanDevirtualizeApplication cenv v cenv.g.generic_equality_er_inner_vref ty args ->
-
+ let tyargsOriginal = tyargs
let tcref, tyargs = StripToNominalTyconRef cenv ty
match tcref.GeneratedHashAndEqualsValues with
| Some (_, vref) -> Some (DevirtualizeApplication cenv env vref ty tyargs args m)
- | _ -> None
+ | _ ->
+ // if type of generic argument has no generated equality operators, covert to "FSharpEqualityComparer_ER<'T>.EqualityComparer.Equals"
+ match cenv.g.fsharpEqualityComparer_ER_Equals_vref.TryDeref with
+ | ValueNone -> None // referencing old version of FSharp.Core.dll
+ | _ -> Some (DevirtualizeApplication cenv env cenv.g.fsharpEqualityComparer_ER_Equals_vref ty tyargsOriginal args m)
// Optimize/analyze calls to LanguagePrimitives.HashCompare.GenericEqualityWithComparerFast
| Expr.Val (v, _, _), [ty], _ when CanDevirtualizeApplication cenv v cenv.g.generic_equality_withc_inner_vref ty args ->
@@ -2634,23 +2638,35 @@ and TryDevirtualizeApplication cenv env (f, tyargs, args, m) =
Some (DevirtualizeApplication cenv env withcEqualsVal ty tyargs args2 m)
| _ -> None
- // Optimize/analyze calls to LanguagePrimitives.HashCompare.GenericEqualityWithComparer
+ // Optimize/analyze calls to LanguagePrimitives.HashCompare.GenericEqualityIntrinsic
| Expr.Val (v, _, _), [ty], _ when CanDevirtualizeApplication cenv v cenv.g.generic_equality_per_inner_vref ty args && not(isRefTupleTy cenv.g ty) ->
+ let tyargsOriginal = tyargs
let tcref, tyargs = StripToNominalTyconRef cenv ty
match tcref.GeneratedHashAndEqualsWithComparerValues, args with
| Some (_, _, withcEqualsVal), [x; y] ->
let args2 = [x; mkRefTupledNoTypes cenv.g m [mkCoerceExpr(y, cenv.g.obj_ty, m, ty); (mkCallGetGenericPEREqualityComparer cenv.g m)]]
Some (DevirtualizeApplication cenv env withcEqualsVal ty tyargs args2 m)
- | _ -> None
+ | _ ->
+ // if type of generic argument has no generated equality operators, covert to "FSharpEqualityComparer_PER<'T>.EqualityComparer.Equals"
+ match cenv.g.fsharpEqualityComparer_PER_Equals_vref.TryDeref with
+ | ValueNone -> None // referencing old version of FSharp.Core.dll
+ | _ -> Some (DevirtualizeApplication cenv env cenv.g.fsharpEqualityComparer_PER_Equals_vref ty tyargsOriginal args m)
+
// Optimize/analyze calls to LanguagePrimitives.HashCompare.GenericHashIntrinsic
| Expr.Val (v, _, _), [ty], _ when CanDevirtualizeApplication cenv v cenv.g.generic_hash_inner_vref ty args ->
+ let tyargsOriginal = tyargs
let tcref, tyargs = StripToNominalTyconRef cenv ty
match tcref.GeneratedHashAndEqualsWithComparerValues, args with
| Some (_, withcGetHashCodeVal, _), [x] ->
let args2 = [x; mkCallGetGenericEREqualityComparer cenv.g m]
Some (DevirtualizeApplication cenv env withcGetHashCodeVal ty tyargs args2 m)
- | _ -> None
+ | _ ->
+ // if type of generic argument has no generated equality operators, covert to "FSharpEqualityComparer_PER<'T>.EqualityComparer.GetHashCode"
+ match cenv.g.fsharpEqualityComparer_GetHashCode_vref.TryDeref with
+ | ValueNone -> None // referencing old version of FSharp.Core.dll
+ | _ -> Some (DevirtualizeApplication cenv env cenv.g.fsharpEqualityComparer_GetHashCode_vref ty tyargsOriginal args m)
+
// Optimize/analyze calls to LanguagePrimitives.HashCompare.GenericHashWithComparerIntrinsic
| Expr.Val (v, _, _), [ty], _ when CanDevirtualizeApplication cenv v cenv.g.generic_hash_withc_inner_vref ty args ->
@@ -2704,6 +2720,24 @@ and TryDevirtualizeApplication cenv env (f, tyargs, args, m) =
match vref with
| Some vref -> Some (DevirtualizeApplication cenv env vref ty tyargs (mkCallGetGenericPEREqualityComparer cenv.g m :: args) m)
| None -> None
+
+ // "GenericEqualityIntrinsic" when found in a generic context, convert to "FSharpEqualityComparer_PER<'T>.EqualityComparer.Equals"
+ | Expr.Val(v, _, _), [(TType_var t) as ty], _ when (not cenv.g.compilingFslib) && valRefEq cenv.g v cenv.g.generic_equality_per_inner_vref && t.Rigidity = TyparRigidity.Rigid ->
+ match cenv.g.fsharpEqualityComparer_PER_Equals_vref.TryDeref with
+ | ValueNone -> None // referencing old version of FSharp.Core.dll
+ | _ -> Some (DevirtualizeApplication cenv env cenv.g.fsharpEqualityComparer_PER_Equals_vref ty tyargs args m)
+
+ // "GenericEqualityERIntrinsic" when found in a generic context, convert to "FSharpEqualityComparer_ER<'T>.EqualityComparer.Equals"
+ | Expr.Val(v, _, _), [(TType_var t) as ty], _ when (not cenv.g.compilingFslib) && valRefEq cenv.g v cenv.g.generic_equality_er_inner_vref && t.Rigidity = TyparRigidity.Rigid ->
+ match cenv.g.fsharpEqualityComparer_ER_Equals_vref.TryDeref with
+ | ValueNone -> None // referencing old version of FSharp.Core.dll
+ | _ -> Some (DevirtualizeApplication cenv env cenv.g.fsharpEqualityComparer_ER_Equals_vref ty tyargs args m)
+
+ // "GenericHashIntrinsic" when found in a generic context, convert to "FSharpEqualityComparer_PER<'T>.EqualityComparer.GetHashCode"
+ | Expr.Val(v, _, _), [(TType_var t) as ty], _ when (not cenv.g.compilingFslib) && valRefEq cenv.g v cenv.g.generic_hash_inner_vref && t.Rigidity = TyparRigidity.Rigid ->
+ match cenv.g.fsharpEqualityComparer_GetHashCode_vref.TryDeref with
+ | ValueNone -> None // referencing old version of FSharp.Core.dll
+ | _ -> Some (DevirtualizeApplication cenv env cenv.g.fsharpEqualityComparer_GetHashCode_vref ty tyargs args m)
// Optimize/analyze calls to LanguagePrimitives.HashCompare.GenericComparisonWithComparerIntrinsic for tuple types
| Expr.Val (v, _, _), [ty], _ when valRefEq cenv.g v cenv.g.generic_comparison_withc_inner_vref && isRefTupleTy cenv.g ty ->
diff --git a/src/fsharp/TcGlobals.fs b/src/fsharp/TcGlobals.fs
index edc894a3f06..0543a774927 100755
--- a/src/fsharp/TcGlobals.fs
+++ b/src/fsharp/TcGlobals.fs
@@ -588,6 +588,10 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
let v_generic_comparison_inner_info = makeIntrinsicValRef(fslib_MFHashCompare_nleref, "GenericComparisonIntrinsic" , None , None , [vara], mk_compare_sig varaTy)
let v_generic_comparison_withc_inner_info = makeIntrinsicValRef(fslib_MFHashCompare_nleref, "GenericComparisonWithComparerIntrinsic", None , None , [vara], mk_compare_withc_sig varaTy)
+ let v_FSharpEqualityComparer_PER_Equals_info = makeIntrinsicValRef(fslib_MFHashCompare_nleref, "FSharpEqualityComparer_PER_Equals" , None , None , [vara], mk_rel_sig varaTy)
+ let v_FSharpEqualityComparer_GetHashCode_info = makeIntrinsicValRef(fslib_MFHashCompare_nleref, "FSharpEqualityComparer_GetHashCode", None , None , [vara], mk_hash_sig varaTy)
+ let v_FSharpEqualityComparer_ER_Equals_info = makeIntrinsicValRef(fslib_MFHashCompare_nleref, "FSharpEqualityComparer_ER_Equals" , None , None , [vara], mk_rel_sig varaTy)
+
let v_generic_hash_inner_info = makeIntrinsicValRef(fslib_MFHashCompare_nleref, "GenericHashIntrinsic" , None , None , [vara], mk_hash_sig varaTy)
let v_generic_hash_withc_inner_info = makeIntrinsicValRef(fslib_MFHashCompare_nleref, "GenericHashWithComparerIntrinsic" , None , None , [vara], mk_hash_withc_sig varaTy)
@@ -1235,6 +1239,9 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
member __.generic_hash_withc_outer_info = v_generic_hash_withc_outer_info
member val generic_hash_inner_vref = ValRefForIntrinsic v_generic_hash_inner_info
member val generic_hash_withc_inner_vref = ValRefForIntrinsic v_generic_hash_withc_inner_info
+ member val fsharpEqualityComparer_ER_Equals_vref = ValRefForIntrinsic v_FSharpEqualityComparer_ER_Equals_info
+ member val fsharpEqualityComparer_PER_Equals_vref = ValRefForIntrinsic v_FSharpEqualityComparer_PER_Equals_info
+ member val fsharpEqualityComparer_GetHashCode_vref = ValRefForIntrinsic v_FSharpEqualityComparer_GetHashCode_info
member val reference_equality_inner_vref = ValRefForIntrinsic v_reference_equality_inner_info
diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/ComparersRegression.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/ComparersRegression.fs
index f971eb1c18e..afa3cd2587b 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core/ComparersRegression.fs
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core/ComparersRegression.fs
@@ -5183,41 +5183,48 @@ type GeneratedTests () =
[]
member __.``SBytes.Collection.ArrayArray C.I.compare``() =
validate (SBytes.Collection.ArrayArray) C.I.compare [|
- 0;1;-1;1;1;-1;-1;-1;-1;-1;-1;0;-1;1;1;-1;-1;-1;-1;-1;1;1;0;1;1;-1;-1;-1;-1;-1;-1;-1;-1;0;-1;-1;-1;-1;-1;-1;
- -1;-1;-1;1;0;-1;-1;-1;-1;-1;1;1;1;1;1;0;1;-1;1;1;1;1;1;1;1;-1;0;-1;1;1;1;1;1;1;1;1;1;0;1;1;
- 1;1;1;1;1;-1;-1;-1;0;-1;1;1;1;1;1;-1;-1;-1;1;0
+ 0;-255;-127;-128;-129;-1;-1;-1;-1;-1;255;0;128;127;126;-1;-1;
+ -1;-1;-1;127;-128;0;-1;-2;-1;-1;-1;-1;-1;128;-127;1;0;-1;-1;
+ -1;-1;-1;-1;129;-126;2;1;0;-1;-1;-1;-1;-1;1;1;1;1;1;0;-255;
+ -127;-128;-129;1;1;1;1;1;255;0;128;127;126;1;1;1;1;1;127;
+ -128;0;-1;-2;1;1;1;1;1;128;-127;1;0;-1;1;1;1;1;1;129;-126;
+ 2;1;0
|]
[]
member __.``SBytes.Collection.ArrayArray C.I.less_than``() =
validate (SBytes.Collection.ArrayArray) C.I.less_than [|
- 0;0;1;0;0;1;1;1;1;1;1;0;1;0;0;1;1;1;1;1;0;0;0;0;0;1;1;1;1;1;1;1;1;0;1;1;1;1;1;1;
- 1;1;1;0;0;1;1;1;1;1;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;1;0;1;0;0;0;0;0;0;0;0;0;0;0;0;
- 0;0;0;0;0;1;1;1;0;1;0;0;0;0;0;1;1;1;0;0
+ 0;1;1;1;1;1;1;1;1;1;0;0;0;0;0;1;1;1;1;1;0;1;0;1;1;1;
+ 1;1;1;1;0;1;0;0;1;1;1;1;1;1;0;1;0;0;0;1;1;1;1;1;0;0;
+ 0;0;0;0;1;1;1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;
+ 1;1;0;0;0;0;0;0;1;0;0;1;0;0;0;0;0;0;1;0;0;0
|]
[]
member __.``SBytes.Collection.ArrayArray C.I.less_or_equal``() =
validate (SBytes.Collection.ArrayArray) C.I.less_or_equal [|
- 1;0;1;0;0;1;1;1;1;1;1;1;1;0;0;1;1;1;1;1;0;0;1;0;0;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;
- 1;1;1;0;1;1;1;1;1;1;0;0;0;0;0;1;0;1;0;0;0;0;0;0;0;1;1;1;0;0;0;0;0;0;0;0;0;1;0;0;
- 0;0;0;0;0;1;1;1;1;1;0;0;0;0;0;1;1;1;0;1
+ 1;1;1;1;1;1;1;1;1;1;0;1;0;0;0;1;1;1;1;1;0;1;1;1;1;1;
+ 1;1;1;1;0;1;0;1;1;1;1;1;1;1;0;1;0;0;1;1;1;1;1;1;0;0;
+ 0;0;0;1;1;1;1;1;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;1;1;
+ 1;1;0;0;0;0;0;0;1;0;1;1;0;0;0;0;0;0;1;0;0;1
|]
[]
member __.``SBytes.Collection.ArrayArray C.I.greater_than``() =
validate (SBytes.Collection.ArrayArray) C.I.greater_than [|
- 0;1;0;1;1;0;0;0;0;0;0;0;0;1;1;0;0;0;0;0;1;1;0;1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;
- 0;0;0;1;0;0;0;0;0;0;1;1;1;1;1;0;1;0;1;1;1;1;1;1;1;0;0;0;1;1;1;1;1;1;1;1;1;0;1;1;
- 1;1;1;1;1;0;0;0;0;0;1;1;1;1;1;0;0;0;1;0
+ 0;0;0;0;0;0;0;0;0;0;1;0;1;1;1;0;0;0;0;0;1;0;0;0;0;0;
+ 0;0;0;0;1;0;1;0;0;0;0;0;0;0;1;0;1;1;0;0;0;0;0;0;1;1;
+ 1;1;1;0;0;0;0;0;1;1;1;1;1;1;0;1;1;1;1;1;1;1;1;1;0;0;
+ 0;0;1;1;1;1;1;1;0;1;0;0;1;1;1;1;1;1;0;1;1;0
|]
[]
member __.``SBytes.Collection.ArrayArray C.I.greater_or_equal``() =
validate (SBytes.Collection.ArrayArray) C.I.greater_or_equal [|
- 1;1;0;1;1;0;0;0;0;0;0;1;0;1;1;0;0;0;0;0;1;1;1;1;1;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;
- 0;0;0;1;1;0;0;0;0;0;1;1;1;1;1;1;1;0;1;1;1;1;1;1;1;0;1;0;1;1;1;1;1;1;1;1;1;1;1;1;
- 1;1;1;1;1;0;0;0;1;0;1;1;1;1;1;0;0;0;1;1
+ 1;0;0;0;0;0;0;0;0;0;1;1;1;1;1;0;0;0;0;0;1;0;1;0;0;0;
+ 0;0;0;0;1;0;1;1;0;0;0;0;0;0;1;0;1;1;1;0;0;0;0;0;1;1;
+ 1;1;1;1;0;0;0;0;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;0;1;
+ 0;0;1;1;1;1;1;1;0;1;1;0;1;1;1;1;1;1;0;1;1;1
|]
[]
@@ -5247,41 +5254,48 @@ type GeneratedTests () =
[]
member __.``SBytes.Collection.ArrayArray C.N.compare``() =
validate (SBytes.Collection.ArrayArray) C.N.compare [|
- 0;1;-1;1;1;-1;-1;-1;-1;-1;-1;0;-1;1;1;-1;-1;-1;-1;-1;1;1;0;1;1;-1;-1;-1;-1;-1;-1;-1;-1;0;-1;-1;-1;-1;-1;-1;
- -1;-1;-1;1;0;-1;-1;-1;-1;-1;1;1;1;1;1;0;1;-1;1;1;1;1;1;1;1;-1;0;-1;1;1;1;1;1;1;1;1;1;0;1;1;
- 1;1;1;1;1;-1;-1;-1;0;-1;1;1;1;1;1;-1;-1;-1;1;0
+ 0;-255;-127;-128;-129;-1;-1;-1;-1;-1;255;0;128;127;126;-1;-1;
+ -1;-1;-1;127;-128;0;-1;-2;-1;-1;-1;-1;-1;128;-127;1;0;-1;-1;
+ -1;-1;-1;-1;129;-126;2;1;0;-1;-1;-1;-1;-1;1;1;1;1;1;0;-255;
+ -127;-128;-129;1;1;1;1;1;255;0;128;127;126;1;1;1;1;1;127;
+ -128;0;-1;-2;1;1;1;1;1;128;-127;1;0;-1;1;1;1;1;1;129;-126;
+ 2;1;0
|]
[]
member __.``SBytes.Collection.ArrayArray C.N.less_than``() =
validate (SBytes.Collection.ArrayArray) C.N.less_than [|
- 0;0;1;0;0;1;1;1;1;1;1;0;1;0;0;1;1;1;1;1;0;0;0;0;0;1;1;1;1;1;1;1;1;0;1;1;1;1;1;1;
- 1;1;1;0;0;1;1;1;1;1;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;1;0;1;0;0;0;0;0;0;0;0;0;0;0;0;
- 0;0;0;0;0;1;1;1;0;1;0;0;0;0;0;1;1;1;0;0
+ 0;1;1;1;1;1;1;1;1;1;0;0;0;0;0;1;1;1;1;1;0;1;0;1;1;1;
+ 1;1;1;1;0;1;0;0;1;1;1;1;1;1;0;1;0;0;0;1;1;1;1;1;0;0;
+ 0;0;0;0;1;1;1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;
+ 1;1;0;0;0;0;0;0;1;0;0;1;0;0;0;0;0;0;1;0;0;0
|]
[]
member __.``SBytes.Collection.ArrayArray C.N.less_or_equal``() =
validate (SBytes.Collection.ArrayArray) C.N.less_or_equal [|
- 1;0;1;0;0;1;1;1;1;1;1;1;1;0;0;1;1;1;1;1;0;0;1;0;0;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;
- 1;1;1;0;1;1;1;1;1;1;0;0;0;0;0;1;0;1;0;0;0;0;0;0;0;1;1;1;0;0;0;0;0;0;0;0;0;1;0;0;
- 0;0;0;0;0;1;1;1;1;1;0;0;0;0;0;1;1;1;0;1
+ 1;1;1;1;1;1;1;1;1;1;0;1;0;0;0;1;1;1;1;1;0;1;1;1;1;1;
+ 1;1;1;1;0;1;0;1;1;1;1;1;1;1;0;1;0;0;1;1;1;1;1;1;0;0;
+ 0;0;0;1;1;1;1;1;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;1;1;
+ 1;1;0;0;0;0;0;0;1;0;1;1;0;0;0;0;0;0;1;0;0;1
|]
[]
member __.``SBytes.Collection.ArrayArray C.N.greater_than``() =
validate (SBytes.Collection.ArrayArray) C.N.greater_than [|
- 0;1;0;1;1;0;0;0;0;0;0;0;0;1;1;0;0;0;0;0;1;1;0;1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;
- 0;0;0;1;0;0;0;0;0;0;1;1;1;1;1;0;1;0;1;1;1;1;1;1;1;0;0;0;1;1;1;1;1;1;1;1;1;0;1;1;
- 1;1;1;1;1;0;0;0;0;0;1;1;1;1;1;0;0;0;1;0
+ 0;0;0;0;0;0;0;0;0;0;1;0;1;1;1;0;0;0;0;0;1;0;0;0;0;0;
+ 0;0;0;0;1;0;1;0;0;0;0;0;0;0;1;0;1;1;0;0;0;0;0;0;1;1;
+ 1;1;1;0;0;0;0;0;1;1;1;1;1;1;0;1;1;1;1;1;1;1;1;1;0;0;
+ 0;0;1;1;1;1;1;1;0;1;0;0;1;1;1;1;1;1;0;1;1;0
|]
[]
member __.``SBytes.Collection.ArrayArray C.N.greater_or_equal``() =
validate (SBytes.Collection.ArrayArray) C.N.greater_or_equal [|
- 1;1;0;1;1;0;0;0;0;0;0;1;0;1;1;0;0;0;0;0;1;1;1;1;1;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;
- 0;0;0;1;1;0;0;0;0;0;1;1;1;1;1;1;1;0;1;1;1;1;1;1;1;0;1;0;1;1;1;1;1;1;1;1;1;1;1;1;
- 1;1;1;1;1;0;0;0;1;0;1;1;1;1;1;0;0;0;1;1
+ 1;0;0;0;0;0;0;0;0;0;1;1;1;1;1;0;0;0;0;0;1;0;1;0;0;0;
+ 0;0;0;0;1;0;1;1;0;0;0;0;0;0;1;0;1;1;1;0;0;0;0;0;1;1;
+ 1;1;1;1;0;0;0;0;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;0;1;
+ 0;0;1;1;1;1;1;1;0;1;1;0;1;1;1;1;1;1;0;1;1;1
|]
[]
diff --git a/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs b/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs
index 0697e5ef6e8..9787480b4bf 100644
--- a/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs
+++ b/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs
@@ -1784,10 +1784,16 @@ Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_InputAr
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_InputMustBeNonNegativeString()
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_InputSequenceEmptyString()
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_NoNegateMinValueString()
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]: System.Collections.Generic.EqualityComparer`1[T] EqualityComparer
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]: System.Collections.Generic.EqualityComparer`1[T] get_EqualityComparer()
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]: System.Collections.Generic.EqualityComparer`1[T] EqualityComparer
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]: System.Collections.Generic.EqualityComparer`1[T] get_EqualityComparer()
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FastEqualsTuple2[T1,T2](System.Collections.IEqualityComparer, System.Tuple`2[T1,T2], System.Tuple`2[T1,T2])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FastEqualsTuple3[T1,T2,T3](System.Collections.IEqualityComparer, System.Tuple`3[T1,T2,T3], System.Tuple`3[T1,T2,T3])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FastEqualsTuple4[T1,T2,T3,T4](System.Collections.IEqualityComparer, System.Tuple`4[T1,T2,T3,T4], System.Tuple`4[T1,T2,T3,T4])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FastEqualsTuple5[T1,T2,T3,T4,T5](System.Collections.IEqualityComparer, System.Tuple`5[T1,T2,T3,T4,T5], System.Tuple`5[T1,T2,T3,T4,T5])
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FSharpEqualityComparer_ER_Equals[T](T, T)
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FSharpEqualityComparer_PER_Equals[T](T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean GenericEqualityERIntrinsic[T](T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean GenericEqualityIntrinsic[T](T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean GenericEqualityWithComparerIntrinsic[T](System.Collections.IEqualityComparer, T, T)
@@ -1804,12 +1810,15 @@ Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FastHashTuple2[T1,T2
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FastHashTuple3[T1,T2,T3](System.Collections.IEqualityComparer, System.Tuple`3[T1,T2,T3])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FastHashTuple4[T1,T2,T3,T4](System.Collections.IEqualityComparer, System.Tuple`4[T1,T2,T3,T4])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FastHashTuple5[T1,T2,T3,T4,T5](System.Collections.IEqualityComparer, System.Tuple`5[T1,T2,T3,T4,T5])
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FSharpEqualityComparer_GetHashCode[T](T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 GenericComparisonIntrinsic[T](T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 GenericComparisonWithComparerIntrinsic[T](System.Collections.IComparer, T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 GenericHashIntrinsic[T](T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 GenericHashWithComparerIntrinsic[T](System.Collections.IEqualityComparer, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 LimitedGenericHashIntrinsic[T](Int32, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 PhysicalHashIntrinsic[T](T)
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]
Microsoft.FSharp.Core.LanguagePrimitives+IntrinsicFunctions: Boolean TypeTestFast[T](System.Object)
Microsoft.FSharp.Core.LanguagePrimitives+IntrinsicFunctions: Boolean TypeTestGeneric[T](System.Object)
Microsoft.FSharp.Core.LanguagePrimitives+IntrinsicFunctions: Char GetString(System.String, Int32)
diff --git a/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs b/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs
index de52090c733..a74f7e67ca8 100644
--- a/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs
+++ b/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs
@@ -1784,10 +1784,16 @@ Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_InputAr
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_InputMustBeNonNegativeString()
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_InputSequenceEmptyString()
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_NoNegateMinValueString()
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]: System.Collections.Generic.EqualityComparer`1[T] EqualityComparer
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]: System.Collections.Generic.EqualityComparer`1[T] get_EqualityComparer()
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]: System.Collections.Generic.EqualityComparer`1[T] EqualityComparer
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]: System.Collections.Generic.EqualityComparer`1[T] get_EqualityComparer()
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FastEqualsTuple2[T1,T2](System.Collections.IEqualityComparer, System.Tuple`2[T1,T2], System.Tuple`2[T1,T2])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FastEqualsTuple3[T1,T2,T3](System.Collections.IEqualityComparer, System.Tuple`3[T1,T2,T3], System.Tuple`3[T1,T2,T3])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FastEqualsTuple4[T1,T2,T3,T4](System.Collections.IEqualityComparer, System.Tuple`4[T1,T2,T3,T4], System.Tuple`4[T1,T2,T3,T4])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FastEqualsTuple5[T1,T2,T3,T4,T5](System.Collections.IEqualityComparer, System.Tuple`5[T1,T2,T3,T4,T5], System.Tuple`5[T1,T2,T3,T4,T5])
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FSharpEqualityComparer_ER_Equals[T](T, T)
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean FSharpEqualityComparer_PER_Equals[T](T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean GenericEqualityERIntrinsic[T](T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean GenericEqualityIntrinsic[T](T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean GenericEqualityWithComparerIntrinsic[T](System.Collections.IEqualityComparer, T, T)
@@ -1804,12 +1810,15 @@ Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FastHashTuple2[T1,T2
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FastHashTuple3[T1,T2,T3](System.Collections.IEqualityComparer, System.Tuple`3[T1,T2,T3])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FastHashTuple4[T1,T2,T3,T4](System.Collections.IEqualityComparer, System.Tuple`4[T1,T2,T3,T4])
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FastHashTuple5[T1,T2,T3,T4,T5](System.Collections.IEqualityComparer, System.Tuple`5[T1,T2,T3,T4,T5])
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 FSharpEqualityComparer_GetHashCode[T](T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 GenericComparisonIntrinsic[T](T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 GenericComparisonWithComparerIntrinsic[T](System.Collections.IComparer, T, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 GenericHashIntrinsic[T](T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 GenericHashWithComparerIntrinsic[T](System.Collections.IEqualityComparer, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 LimitedGenericHashIntrinsic[T](Int32, T)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Int32 PhysicalHashIntrinsic[T](T)
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]
+Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]
Microsoft.FSharp.Core.LanguagePrimitives+IntrinsicFunctions: Boolean TypeTestFast[T](System.Object)
Microsoft.FSharp.Core.LanguagePrimitives+IntrinsicFunctions: Boolean TypeTestGeneric[T](System.Object)
Microsoft.FSharp.Core.LanguagePrimitives+IntrinsicFunctions: Char GetString(System.String, Int32)
diff --git a/tests/fsharpqa/Source/CodeGen/EmittedIL/QueryExpressionStepping/Linq101Joins01.il.bsl b/tests/fsharpqa/Source/CodeGen/EmittedIL/QueryExpressionStepping/Linq101Joins01.il.bsl
index f985ce06194..8e7e62263fd 100644
--- a/tests/fsharpqa/Source/CodeGen/EmittedIL/QueryExpressionStepping/Linq101Joins01.il.bsl
+++ b/tests/fsharpqa/Source/CodeGen/EmittedIL/QueryExpressionStepping/Linq101Joins01.il.bsl
@@ -1,5 +1,5 @@
-// Microsoft (R) .NET Framework IL Disassembler. Version 4.6.1055.0
+// Microsoft (R) .NET Framework IL Disassembler. Version 4.8.3928.0
// Copyright (c) Microsoft Corporation. All rights reserved.
@@ -13,7 +13,7 @@
.assembly extern FSharp.Core
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
- .ver 4:5:0:0
+ .ver 4:7:0:0
}
.assembly extern Utils
{
@@ -38,20 +38,20 @@
}
.mresource public FSharpSignatureData.Linq101Joins01
{
- // Offset: 0x00000000 Length: 0x00000316
+ // Offset: 0x00000000 Length: 0x000002F8
}
.mresource public FSharpOptimizationData.Linq101Joins01
{
- // Offset: 0x00000320 Length: 0x000000C3
+ // Offset: 0x00000300 Length: 0x000000C3
}
.module Linq101Joins01.exe
-// MVID: {5B9A68C1-151B-685E-A745-0383C1689A5B}
+// MVID: {5EDEADE3-151B-685E-A745-0383E3ADDE5E}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
-// Image base: 0x026A0000
+// Image base: 0x00DC0000
// =============== CLASS MEMBERS DECLARATION ===================
@@ -81,7 +81,7 @@
// Code size 2 (0x2)
.maxstack 8
.language '{AB4F38C9-B6E6-43BA-BE3B-58080B2CCCE3}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
- .line 14,14 : 32,33 'C:\\GitHub\\dsyme\\visualfsharp\\tests\\fsharpqa\\Source\\CodeGen\\EmittedIL\\QueryExpressionStepping\\Linq101Joins01.fs'
+ .line 14,14 : 32,33 'C:\\src\\fsharp\\tests\\fsharpqa\\source\\CodeGen\\EmittedIL\\QueryExpressionStepping\\Linq101Joins01.fs'
IL_0000: ldarg.1
IL_0001: ret
} // end of method q@14::Invoke
@@ -790,54 +790,65 @@
.method public strict virtual instance class [FSharp.Core]Microsoft.FSharp.Linq.QuerySource`2,class [Utils]Utils/Product,string>,object>
Invoke(class [Utils]Utils/Product _arg2) cil managed
{
- // Code size 69 (0x45)
+ // Code size 84 (0x54)
.maxstack 9
.locals init ([0] class [Utils]Utils/Product p,
- [1] string t)
+ [1] string t,
+ [2] object V_2,
+ [3] object V_3,
+ [4] object V_4,
+ [5] object V_5)
.line 40,40 : 9,40 ''
IL_0000: ldarg.1
IL_0001: stloc.0
.line 41,41 : 17,39 ''
IL_0002: ldloc.0
IL_0003: box [Utils]Utils/Product
- IL_0008: ldnull
- IL_0009: call bool [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/HashCompare::GenericEqualityIntrinsic