Skip to content

Commit

Permalink
Added optimize equality switch. Figuring out rules for equality optim…
Browse files Browse the repository at this point in the history
…izations. Thank you Don.
  • Loading branch information
TIHan committed Feb 1, 2019
1 parent 3225121 commit 34a3e84
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/absil/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2582,6 +2582,8 @@ type ILGlobals(primaryScopeRef) =
let m_typ_IntPtr = ILType.Value (mkILNonGenericTySpec (m_mkSysILTypeRef tname_IntPtr))
let m_typ_UIntPtr = ILType.Value (mkILNonGenericTySpec (m_mkSysILTypeRef tname_UIntPtr))

let m_typ_IEquatableT = mkILBoxedType (mkILTySpec (m_mkSysILTypeRef "System.IEquatable`1", [ mkILTyvarTy 1us ]))

member x.primaryAssemblyScopeRef = m_typ_Object.TypeRef.Scope
member x.primaryAssemblyName = m_typ_Object.TypeRef.Scope.AssemblyRef.Name
member x.typ_Object = m_typ_Object
Expand All @@ -2602,6 +2604,7 @@ type ILGlobals(primaryScopeRef) =
member x.typ_Double = m_typ_Double
member x.typ_Bool = m_typ_Bool
member x.typ_Char = m_typ_Char
member x.typ_IEquatableT = m_typ_IEquatableT

/// For debugging
[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
Expand Down
1 change: 1 addition & 0 deletions src/absil/il.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,7 @@ type ILGlobals =
member typ_Double: ILType
member typ_Bool: ILType
member typ_Char: ILType
member typ_IEquatableT: ILType


/// Build the table of commonly used references given functions to find types in system assemblies
Expand Down
7 changes: 7 additions & 0 deletions src/fsharp/CompileOptions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,11 @@ let SetOptimizeOn(tcConfigB : TcConfigBuilder) =
let SetOptimizeSwitch (tcConfigB : TcConfigBuilder) switch =
if (switch = OptionSwitch.On) then SetOptimizeOn(tcConfigB) else SetOptimizeOff(tcConfigB)

let SetSpecificOptimizeSwitch (tcConfigB : TcConfigBuilder) n switch =
match n with
| "equality" -> tcConfigB.optSettings <- { tcConfigB.optSettings with optimizeComparisonLogic = (switch = OptionSwitch.On) }
| _ -> failwithf "dodgy flag %s" n

let SetTailcallSwitch (tcConfigB : TcConfigBuilder) switch =
tcConfigB.emitTailcalls <- (switch = OptionSwitch.On)

Expand Down Expand Up @@ -696,6 +701,8 @@ let codeGenerationFlags isFsi (tcConfigB : TcConfigBuilder) =
let codegen =
[CompilerOption("optimize", tagNone, OptionSwitch (SetOptimizeSwitch tcConfigB) , None,
Some (FSComp.SR.optsOptimize()))
CompilerOption("optimize", tagNone, OptionStringListSwitch (SetSpecificOptimizeSwitch tcConfigB) , None,
Some (FSComp.SR.optsOptimize()))
CompilerOption("tailcalls", tagNone, OptionSwitch (SetTailcallSwitch tcConfigB), None,
Some (FSComp.SR.optsTailcalls()))
CompilerOption("deterministic", tagNone, OptionSwitch (SetDeterministicSwitch tcConfigB), None,
Expand Down
38 changes: 35 additions & 3 deletions src/fsharp/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ type OptimizationSettings =
jitOptUser : bool option
localOptUser : bool option
crossModuleOptUser : bool option
optimizeComparisonLogic : bool
/// size after which we start chopping methods in two, though only at match targets
bigTargetSize : int
/// size after which we start enforcing splitting sub-expressions to new methods, to avoid hitting .NET IL limitations
Expand All @@ -278,6 +279,7 @@ type OptimizationSettings =
{ abstractBigTargets = false
jitOptUser = None
localOptUser = None
optimizeComparisonLogic = false
/// size after which we start chopping methods in two, though only at match targets
bigTargetSize = 100
/// size after which we start enforcing splitting sub-expressions to new methods, to avoid hitting .NET IL limitations
Expand Down Expand Up @@ -2472,7 +2474,34 @@ and DevirtualizeApplication cenv env (vref:ValRef) ty tyargs args m =
let wrap, args = TakeAddressOfStructArgumentIfNeeded cenv vref ty args m
let transformedExpr = wrap (MakeApplicationAndBetaReduce cenv.g (exprForValRef m vref, vref.Type, (if isNil tyargs then [] else [tyargs]), args, m))
OptimizeExpr cenv env transformedExpr


and DevirtualizeGenericEqualityIntrinsic cenv env receiver arg m =
let call = mkCall_IEquatableT_Equals cenv.g m receiver arg
OptimizeExpr cenv env call

/// Check if a type 'ty' implements 'IEquatable<ty>'
and IsIEquatableTy cenv m ty =
let searchTy = mkAppTy cenv.g.system_GenericIEquatable_tcref [ty]
ExistsInEntireHierarchyOfType (fun t -> typeEquiv cenv.g t searchTy) cenv.g cenv.amap m AllowMultiIntfInstantiations.Yes ty

/// Check if a type 'ty' implements 'IStructuralEquatable'
and IsIStructuralEquatableTy cenv m ty =
let searchTy = mkAppTy cenv.g.tcref_System_IStructuralEquatable []
ExistsInEntireHierarchyOfType (fun t -> typeEquiv cenv.g t searchTy) cenv.g cenv.amap m AllowMultiIntfInstantiations.Yes ty

/// Check if a type 'ty' is a structural F# type with default structural equality semantics
and IsGeneratedHashAndEqualsTy g ty =
isAnonRecdTy g ty ||
(isAppTy g ty &&
(let tcref = tcrefOfAppTy g ty
tcref.GeneratedHashAndEqualsValues.IsSome && tcref.GeneratedHashAndEqualsWithComparerValues.IsSome))

/// Check if we can (perhaps optimistically) convert the reduced optimization of 'a = b' to '(a :> IEquatable).Equals(b)'
and CanOptimizeGenericEqualityIntrinsicToIEquatableEquals cenv m ty =
IsIEquatableTy cenv m ty &&
not (isAnyTupleTy cenv.g ty) &&
(IsGeneratedHashAndEqualsTy cenv.g ty || (cenv.settings.optimizeComparisonLogic && not (IsIStructuralEquatableTy cenv m ty)))

and TryDevirtualizeApplication cenv env (f, tyargs, args, m) =
match f, tyargs, args with

Expand Down Expand Up @@ -2529,7 +2558,7 @@ and TryDevirtualizeApplication cenv env (f, tyargs, args, m) =
| _ -> None

// Optimize/analyze calls to LanguagePrimitives.HashCompare.GenericEqualityWithComparer
| Expr.Val(v, _, _), [ty], _ when CanDevirtualizeApplication cenv v cenv.g.generic_equality_per_inner_vref ty args && not(isRefTupleTy cenv.g ty) ->
| Expr.Val(v, _, _), [ty], _ when CanDevirtualizeApplication cenv v cenv.g.generic_equality_withc_outer_vref ty args && not(isRefTupleTy cenv.g ty) ->
let tcref, tyargs = StripToNominalTyconRef cenv ty
match tcref.GeneratedHashAndEqualsWithComparerValues, args with
| Some (_, _, withcEqualsVal), [x; y] ->
Expand Down Expand Up @@ -2599,7 +2628,7 @@ 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

// 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 ->
let tyargs = destRefTupleTy cenv.g ty
Expand Down Expand Up @@ -2668,6 +2697,9 @@ and TryDevirtualizeApplication cenv env (f, tyargs, args, m) =
MightMakeCriticalTailcall = false
Info=UnknownValue})

| Expr.Val(v, _, _), [_], [receiver; arg] when valRefEq cenv.g v cenv.g.generic_equality_per_inner_vref && CanOptimizeGenericEqualityIntrinsicToIEquatableEquals cenv m (tyOfExpr cenv.g receiver) ->
Some(DevirtualizeGenericEqualityIntrinsic cenv env receiver arg m)

| _ -> None

/// Attempt to inline an application of a known value at callsites
Expand Down
1 change: 1 addition & 0 deletions src/fsharp/Optimizer.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type OptimizationSettings =
jitOptUser : bool option
localOptUser : bool option
crossModuleOptUser : bool option
optimizeComparisonLogic : bool
bigTargetSize : int
veryBigExprSize : int
lambdaInlineThreshold : int
Expand Down
18 changes: 18 additions & 0 deletions src/fsharp/TastOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6385,6 +6385,9 @@ let mspec_String_Concat4 (g: TcGlobals) =
let mspec_String_Concat_Array (g: TcGlobals) =
mkILNonGenericStaticMethSpecInTy (g.ilg.typ_String, "Concat", [ mkILArr1DTy g.ilg.typ_String ], g.ilg.typ_String)

let mspec_IEquatableT_Equals (g: TcGlobals) =
mkILNonGenericMethSpecInTy (g.ilg.typ_IEquatableT, ILCallingConv.Instance, "Equals", [ mkILTyvarTy 0us ], g.ilg.typ_Bool)

let fspec_Missing_Value (g: TcGlobals) = IL.mkILFieldSpecInTy(g.iltyp_Missing, "Value", g.iltyp_Missing)

let mkInitializeArrayMethSpec (g: TcGlobals) =
Expand Down Expand Up @@ -6622,6 +6625,21 @@ let mkStaticCall_String_Concat_Array g m arg =
let mspec = mspec_String_Concat_Array g
Expr.Op(TOp.ILCall(false, false, false, false, ValUseFlag.NormalValUse, false, false, mspec.MethodRef, [], [], [g.string_ty]), [], [arg], m)

let mkCall_IEquatableT_Equals g m receiver arg =
let mspec = mspec_IEquatableT_Equals g
let receiverTy = tyOfExpr g receiver
let isStruct = isStructTy g receiverTy

let wrap, finalExpr, valUseFlag =
if isStruct then
let wrap, addrOfReceiver, _, _ = mkExprAddrOfExpr g true false Mutates.NeverMutates receiver None m
wrap, addrOfReceiver, ValUseFlag.PossibleConstrainedCall(receiverTy)
else
id, receiver, ValUseFlag.NormalValUse

Expr.Op(TOp.ILCall(isStruct, false, false, false, valUseFlag, false, false, mspec.MethodRef, [receiverTy], [], [g.bool_ty]), [], [finalExpr; arg], m)
|> wrap

// Quotations can't contain any IL.
// As a result, we aim to get rid of all IL generation in the typechecker and pattern match
// compiler, or else train the quotation generator to understand the generated IL.
Expand Down
2 changes: 2 additions & 0 deletions src/fsharp/TastOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,8 @@ val mkStaticCall_String_Concat3 : TcGlobals -> range -> Expr -> Expr -> Expr ->
val mkStaticCall_String_Concat4 : TcGlobals -> range -> Expr -> Expr -> Expr -> Expr -> Expr
val mkStaticCall_String_Concat_Array : TcGlobals -> range -> Expr -> Expr

val mkCall_IEquatableT_Equals : TcGlobals -> range -> Expr -> Expr -> Expr

//-------------------------------------------------------------------------
// operations primarily associated with the optimization to fix
// up loops to generate .NET code that does not include array bound checks
Expand Down
4 changes: 4 additions & 0 deletions src/fsharp/range.fs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ type range(code1:int64, code2: int64) =

override r.ToString() = sprintf "%s (%d,%d--%d,%d) IsSynthetic=%b" r.FileName r.StartLine r.StartColumn r.EndLine r.EndColumn r.IsSynthetic

interface IEquatable<range> with

member this.Equals(m) = this.Code1 = m.Code1 && this.Code2 = m.Code2

let mkRange f b e =
// remove relative parts from full path
let normalizedFilePath = if Path.IsPathRooted f then try Path.GetFullPath f with _ -> f else f
Expand Down
3 changes: 3 additions & 0 deletions src/fsharp/range.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module public Microsoft.FSharp.Compiler.Range

open System
open System.Text
open System.Collections.Generic
open Internal.Utilities
Expand Down Expand Up @@ -49,6 +50,8 @@ type range =
member MakeSynthetic : unit -> range
member ToShortString : unit -> string
static member Zero : range

interface IEquatable<range>

/// This view of range marks uses file indexes explicitly
val mkFileIndexRange : FileIndex -> pos -> pos -> range
Expand Down

0 comments on commit 34a3e84

Please sign in to comment.