Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[CompilerPerf] Faster equality in generic contexts #5112

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ff6b02d
Helper function for FastGenericEqualityComparerTable
manofstick Jun 5, 2018
fbf7cc0
Use the default equality comparer where applicable
manofstick Jun 5, 2018
4345590
Avoid calls to GenericEqualityObj for known types
manofstick Jun 5, 2018
02c028a
Avoid boxing in the "standard" use of GenericEqualityWithComparerIntr…
manofstick Jun 6, 2018
0e0c69b
Added method to avoid tail calls
manofstick Jun 6, 2018
61f79c6
Implemented hashing
manofstick Jun 6, 2018
11a95c2
Additional use of EqualityComparer.Default
manofstick Jun 6, 2018
09fcb46
Consistent naming
manofstick Jun 6, 2018
b83f0b9
Fixed up by mix up my ERs with my PERs!
manofstick Jun 7, 2018
abdc424
Apply De Morgan's law to make it a bit cleaner
manofstick Jun 7, 2018
3808bc5
Argh, got the PER and ER mixed up even when I tried to fix. Now good …
manofstick Jun 8, 2018
0e70bda
Removed custom delegates by using System.Func
manofstick Jun 8, 2018
ca285aa
Disallow optimization on Nullable types
manofstick Jun 8, 2018
efc150c
More inclusive check for canUseDefaultEqualityComparer
manofstick Jun 9, 2018
5b5e0ff
Updated il output files
manofstick Jun 9, 2018
7d85ee6
Removed IERorPER by splitting calling class into 2, and added helper …
manofstick Jun 11, 2018
fb51116
Changed it all to used EqualityComparer derived classes and restored …
manofstick Jun 13, 2018
6d56009
Removed comparers that matched EqualityComparer<>.Default and code re…
manofstick Jun 14, 2018
036b2c9
Added additional types where EqualityComparer.Default can be used
manofstick Jun 16, 2018
ef8aafe
Save unnecessary type checks when more information is known
manofstick Jun 16, 2018
4e4c60e
Removed now unused objects, and other minor cleanup
manofstick Jun 18, 2018
01734b6
Modified Optimizer to manually inline GenericEqualityIntrinsic and fr…
manofstick Jun 23, 2018
3a30af3
Fixed SurfaceArea
manofstick Jun 23, 2018
9e24a31
Ensure FSharp.Core has the optimization functions (and added comments)
manofstick Jun 23, 2018
4130d6a
Remove IL bloat from this PR as per https://github.com/Microsoft/visu…
manofstick Jun 25, 2018
e7d618b
Remove null checks for IStructuralEquality Value Types
manofstick Jun 26, 2018
a72a1de
Consolidated Type Specific Array Equality functions
manofstick Jun 27, 2018
469f55c
Fix compiler call via FSharpFunc.Invoke rather than direct
manofstick Jun 27, 2018
0e7b688
Replaced duplicated GetHashCode code with inline function
manofstick Jun 28, 2018
4dc1a79
Common array EqualityComparers (for types that previously had special…
manofstick Jun 28, 2018
5b80e90
Fix regression in regards to hash code on fast-path covariant arrays …
manofstick Jul 11, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
635 changes: 326 additions & 309 deletions src/fsharp/FSharp.Core/prim-types.fs

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions src/fsharp/FSharp.Core/prim-types.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,26 @@ namespace Microsoft.FSharp.Core

/// <summary>The F# compiler emits calls to some of the functions in this module as part of the compiled form of some language constructs</summary>
module HashCompare =
[<AbstractClass; Sealed>]
type FSharpEqualityComparer_ER<'T> =
static member EqualityComparer : System.Collections.Generic.EqualityComparer<'T>

[<AbstractClass; Sealed>]
type FSharpEqualityComparer_PER<'T> =
static member EqualityComparer : System.Collections.Generic.EqualityComparer<'T>

/// <summary>A primitive entry point used by the F# compiler for optimization purposes.</summary>
[<CompilerMessage("This function is a primitive library routine used by optimized F# code and should not be used directly", 1204, IsHidden=true)>]
val inline FSharpEqualityComparer_ER_Equals : x:'T -> y:'T -> bool

/// <summary>A primitive entry point used by the F# compiler for optimization purposes.</summary>
[<CompilerMessage("This function is a primitive library routine used by optimized F# code and should not be used directly", 1204, IsHidden=true)>]
val inline FSharpEqualityComparer_PER_Equals : x:'T -> y:'T -> bool

/// <summary>A primitive entry point used by the F# compiler for optimization purposes.</summary>
[<CompilerMessage("This function is a primitive library routine used by optimized F# code and should not be used directly", 1204, IsHidden=true)>]
val inline FSharpEqualityComparer_GetHashCode : x:'T -> int

/// <summary>A primitive entry point used by the F# compiler for optimization purposes.</summary>
[<CompilerMessage("This function is a primitive library routine used by optimized F# code and should not be used directly", 1204, IsHidden=true)>]
val PhysicalHashIntrinsic : input:'T -> int when 'T : not struct
Expand Down
43 changes: 37 additions & 6 deletions src/fsharp/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2328,12 +2328,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 ->
Expand All @@ -2345,23 +2348,33 @@ 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 ->
Expand Down Expand Up @@ -2416,6 +2429,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 ->
Expand Down
7 changes: 7 additions & 0 deletions src/fsharp/TcGlobals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,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)

Expand Down Expand Up @@ -1220,6 +1224,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

Expand Down
17 changes: 17 additions & 0 deletions tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2229,11 +2229,25 @@ Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_InputMu
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_InputSequenceEmptyString()
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.String get_NoNegateMinValueString()
Microsoft.FSharp.Core.LanguagePrimitives+ErrorStrings: System.Type GetType()
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]: Boolean Equals(System.Object)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]: Int32 GetHashCode()
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_ER`1[T]: System.String ToString()
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_ER`1[T]: System.Type GetType()
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]: Boolean Equals(System.Object)
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]: Int32 GetHashCode()
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+FSharpEqualityComparer_PER`1[T]: System.String ToString()
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare+FSharpEqualityComparer_PER`1[T]: System.Type GetType()
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: Boolean Equals(System.Object)
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)
Expand All @@ -2250,13 +2264,16 @@ 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 GetHashCode()
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+HashCompare: System.String ToString()
Microsoft.FSharp.Core.LanguagePrimitives+HashCompare: System.Type GetType()
Microsoft.FSharp.Core.LanguagePrimitives+IntrinsicFunctions: Boolean Equals(System.Object)
Expand Down
Loading